Générer une liste de nombres et leurs homologues négatifs en Python

32

Existe-t-il un one-liner pratique pour générer une liste de nombres et leurs homologues négatifs en Python?

Par exemple, disons que je veux générer une liste avec les nombres 6 à 9 et -6 à -9.

Mon approche actuelle est:

l = [x for x in range(6,10)]
l += [-x for x in l]

Un simple "one-liner" serait:

l = [x for x in range(6,10)] + [y for y in range(-9, -5)]

Cependant, générer deux listes, puis les réunir, semble peu pratique.

upe
la source
3
Les nombres positifs doivent-ils précéder les nombres négatifs?
Erich
2
@Erich Non, la commande n'a pas d'importance dans mon cas.
UPE
6
Si la commande n'a pas d'importance, doit-elle même être une liste? Un ensemble serait-il OK (qui n'est pas ordonné), ou un générateur ou un tuple (les deux étant commandés)?
JG
@JG Je pourrais vouloir créer une figure plus tard. Censément en utilisant scatter, etc. Donc, tout scalar or array-like, shapeirait bien.
UPE
2
La plupart de ces réponses se lisent comme certaines solutions anti-golf; tri, fonctions génératrices, outils itératifs. Je préfère de loin conserver le code que vous avez fourni que la plupart des «réponses».
Peilonrayz

Réponses:

47

Je ne sais pas si la commande est importante, mais vous pouvez créer un tuple et le déballer dans une liste de compréhension.

nums = [y for x in range(6,10) for y in (x,-x)]
print(nums)
[6, -6, 7, -7, 8, -8, 9, -9]
Datanovice
la source
53

Créez une fonction agréable et lisible:

def range_with_negatives(start, end):
    for x in range(start, end):
        yield x
        yield -x

Usage:

list(range_with_negatives(6, 10))

C'est ainsi que vous obtenez une doublure pratique pour tout. Évitez d'essayer de ressembler à un pirate pro magique.

Derte Trdelnik
la source
21
Les préjugés contre les compréhensions de liste / dict sont assez courants sur SO, et ils sont généralement justifiés en disant qu'ils ne sont pas "lisibles". La lisibilité inhérente d'une construction (plutôt que la façon dont elle est utilisée) est largement subjective. Personnellement, je trouve les compréhensions plus lisibles, et cette solution l'est beaucoup moins (Comparez: "notez la tension artérielle de chaque homme de plus de 50 ans" vs "Passez en revue chaque personne. D'accord, sont-ils des hommes? Si oui, alors ..." ). Je les utilise pour cette raison, pas parce que j'essaye de "ressembler" à n'importe quoi. Les longues compréhensions peuvent être brisées sur plusieurs lignes si tel est le problème.
Jez
2
C'est suffisant. Mais c'est là qu'intervient la subjectivité: personnellement, je trouve la solution de liste comp au moins aussi lisible, en termes de tension mentale, que la fonction de générateur. Je voudrais cependant améliorer la lisibilité (ou plutôt, la lisibilité-pour-moi) de la réponse de Datanovice en renommant yà signedValue. Pour moi, la véritable valeur ajoutée de l'approche de définition d'une fonction serait de fournir des résultats dans l'ordre strict de la question posée (positive, puis négative) tout en évitant le problème de la ligne unique de Barmar chaindans laquelle des arguments numériques légèrement différents ont à coder en dur deux fois.
Jez
2
Je suis d'accord avec le sentiment général sur les compréhensions de liste, mais définir une fonction comme celle-ci est également une technique très utile à connaître. (Il est particulièrement puissant de collecter les résultats d'un algorithme récursif en écrivant un générateur récursif et en transmettant le résultat de niveau supérieur à listou autre chose.) Après des années à essayer de codifier la meilleure façon de prendre ces décisions, le seul principe général qui a du sens pour moi : s'il y a un nom évident et bon à donner à quelque chose dans le programme, profitez-en pour le faire (et les fonctions sont l'une des façons dont nous le faisons).
Karl Knechtel
44

Je dirais que la solution la plus simple consiste à déballer deux plages dans une liste à l'aide de l' *opérateur de déballage:

>>> [*range(6, 10), *range(-9, -5)]
[6, 7, 8, 9, -9, -8, -7, -6]

Non seulement c'est la réponse la plus courte proposée à ce jour, mais c'est aussi la plus performante, car elle ne construit qu'une seule liste et n'implique aucun appel de fonction au-delà des deux. range s.

J'ai vérifié cela en testant toutes les réponses à cette question en utilisant le timeit module:

Answer ID Method timeit result
-------------------------------------------------- ------------------------------------------------
(en question) [x pour x dans la plage (6,10)] + [y pour y dans la plage (-9, -5)] 0,843 usec par boucle
(cette réponse) [* plage (6, 10), * plage (-9, -5)] 0,509 usec par boucle
61348876 [y pour x dans la plage (6,10) pour y dans (x, -x)] 0,754 usec par boucle
61349149 liste (range_with_negatives (6, 10)) 0,795 usec par boucle
61348914 liste (itertools.chain (plage (6, 10), plage (-9, -5))) 0,709 usec par boucle
61366995 [signe * x pour signe, x dans itertools.product ((- 1, 1), range (6, 10))] 0,899 usec par boucle
61371302 liste (plage (6, 10)) + liste (plage (-9, -5)) 0,729 usec par boucle
Liste 61367180 (range_with_negs (6, 10)) 1,95 usec par boucle

(test de timeit effectué avec Python 3.6.9 sur mon propre ordinateur (spécifications moyennes))

RoadrunnerWMC
la source
2
Je ne suis pas trop enclin à supposer que les performances sont pertinentes lorsque tout ce que vous avez est un exemple avec <10 éléments, mais c'est clairement la solution la plus simple.
JollyJoker
Comment déterminez-vous le début et la fin des valeurs négatives sans les coder en dur?
aldokkani Il y a
2
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC
21

Vous pouvez utiliser itertools.chain()pour concaténer les deux plages.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))
Barmar
la source
1
J'utiliserais 'range (-6, -10, -1)' pour la clarté de l'ordre n'a pas d'importance (et remplacer les 6 et 10 par des variables)
Maarten Fabré
8

Vous pouvez utiliser itertools.product, qui est le produit cartésien.

[sign*x for sign, x in product((-1, 1), range(6, 10))]
[-6, -7, -8, -9, 6, 7, 8, 9]

Cela peut être plus lent car vous utilisez la multiplication, mais devrait être facile à lire.

Si vous souhaitez une solution purement fonctionnelle, vous pouvez également importer itertools.starmapet operator.mul:

from itertools import product, starmap
from operator import mul

list(starmap(mul, product((-1, 1), range(6, 10))))

Cependant, cela est moins lisible.

Frank Vel
la source
6
Je trouve l'utilisation de product, starmapet opertaor.mulinutilement obtuse par rapport aux compréhensions de listes imbriquées, mais j'approuve la suggestion d'utiliser la multiplication. [x * sign for sign in (1, -1) for x in range(6, 10)]n'est que 10% plus lent que [y for x in range(6, 10) for y in (x, -x)], et dans les cas où l'ordre est important, plus de 3 fois plus rapide que le tri de l'approche basée sur les tuples.
ApproachingDarknessFish
C'est une bonne idée, mais peut-être un peu trop spécifique aux détails du problème. Les techniques proposées par d'autres s'appliquent plus généralement.
Karl Knechtel
@ApproachingDarknessFish Je suis d'accord starmapet muldevrait être évité, mais je pense productque cela le rend plus lisible, car il regroupe les itérateurs et leurs éléments séparément. Les boucles doubles dans la compréhension des listes peuvent également prêter à confusion, en raison de leur ordre inattendu.
Frank Vel
5

Vous êtes vraiment proche, avec la combinaison de deux rangeobjets. Mais il existe un moyen plus simple de le faire:

>>> list(range(6, 10)) + list(range(-9, -5))
[6, 7, 8, 9, -9, -8, -7, -6]

Autrement dit, convertissez chaque rangeobjet en une liste, puis concaténez les deux listes.

Une autre approche, en utilisant itertools:

>>> list(itertools.chain(range(6, 10), range(-9, -5)))
[6, 7, 8, 9, -9, -8, -7, -6]

itertools.chain()c'est comme un généralisé +: au lieu d'ajouter deux listes, il enchaîne un itérateur après l'autre pour en faire un "super-itérateur". Passez ensuite cela à list()et vous obtenez une liste concrète, avec tous les numéros que vous voulez en mémoire.

Greg Ward
la source
4
Cette réponse est utile simplement pour la compréhension qui [x for x in ...]est mieux orthographiée list(...).
Karl Knechtel
3

Pesant avec encore une autre possibilité.

Si vous voulez la lisibilité, votre doublure d'origine était plutôt bonne, mais je changerais les plages pour qu'elles soient les mêmes car je pense que les limites négatives rendent les choses moins claires.

[x for x in range(6, 10)] + [-x for x in range(6, 10)]
KyleL
la source
3

L'OMI, l'approche utilisée itertools.chaindans quelques autres réponses est certainement la plus propre de celles fournies jusqu'à présent.

Cependant, comme dans votre cas, l'ordre des valeurs n'a pas d'importance , vous pouvez éviter d'avoir à définir deux rangeobjets explicites , et ainsi éviter de faire toutes les mathématiques off-by-one nécessaires à une rangeindexation négative , en utilisant itertools.chain.from_iterable:

>>> import itertools
>>> list(itertools.chain.from_iterable((x, -x) for x in range(6, 10)))
[6, -6, 7, -7, 8, -8, 9, -9]

Tad verbeux, mais assez lisible.

Une autre option similaire consiste à utiliser le décompactage tuple / argument avec plain chain:

>>> list(itertools.chain(*((x, -x) for x in range(6, 10))))
[6, -6, 7, -7, 8, -8, 9, -9]

Plus concis, mais je trouve que le déballage des tuples est plus difficile à analyser dans un scan rapide.

hBy2Py
la source
3

Ceci est une variation sur un thème (voir @Derte trdelník de réponse ) suivant la philosophie de itertoolslaquelle

les blocs de construction de l'itérateur [...] sont utiles seuls ou en combinaison.

L'idée est que, pendant que nous définissons une nouvelle fonction, nous pouvons aussi bien la rendre générique:

def interleaved_negatives(it):
    for i in it:
        yield i
        yield -i

et l'appliquer à un rangeitérateur particulier :

list(interleaved_negatives(range(6, 10)))
PiCTo
la source
1

Si vous souhaitez conserver l'ordre que vous avez spécifié, vous pouvez utiliser le générateur de plage intégré de Python avec un conditionnel:

def range_with_negs(start, stop):
    for value in range(-(stop-1), stop):      
        if (value <= -start) or (value >= start):
            yield value

Ce qui vous donne la sortie:

In [1]: list(range_with_negs(6, 10))
Out[1]: [-9, -8, -7, -6, 6, 7, 8, 9]

Et fonctionne également avec 0 comme point de départ pour la gamme complète.

Jeff
la source
2
Ceci est très inefficace pour les grandes valeurs de start.
Solomon Ucko
1

Il peut y avoir différentes façons de faire le travail.

Variables données: 1. début = 6 2. arrêt = 10

Vous pouvez également essayer ceci, pour différentes approches:

def mirror_numbers(start,stop):
  if start<stop:
    val=range(start,stop)
    return [j if i < len(val) else -j for i,j in enumerate([x for x in val]*2) ]

mirror_numbers(6,10)
hp_elite
la source
-1

Tout comme les symétries.

a = 6 b = 10

nums = [x + y pour x dans (- (a + b-1), 0) pour y dans la plage (a, b)]

Le résultat doit être -9, -8, ..., 8, 9.

Je crois que l'expression nums peut être améliorée, ce qui suit "in" et "range" me semble toujours déséquilibré.

CSQL
la source