Genera un elenco di numeri e le loro controparti negative in Python


32

Esiste un pratico one-liner per generare un elenco di numeri e le loro controparti negative in Python?

Ad esempio, supponiamo di voler generare un elenco con i numeri da 6 a 9 e da -6 a -9.

Il mio approccio attuale è:

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

Un semplice "one-liner" sarebbe:

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

Tuttavia, generare due elenchi e poi unirli insieme sembra scomodo.


3
I numeri positivi devono precedere quelli negativi?
Erich,

2
@Erich No, l'ordine non ha importanza nel mio caso.
up

6
Se l'ordine non ha importanza, deve anche essere un elenco? Un set sarebbe OK (che non è ordinato), o un generatore o tupla (entrambi ordinati)?
JG

@JG Potrei voler creare una figura in seguito. Presumibilmente usando scatter ecc. Quindi qualsiasi scalar or array-like, shapeandrebbe bene.
up

2
La maggior parte di queste risposte sta leggendo come alcune soluzioni anti-golf; ordinamento, funzioni del generatore, itertools. Preferirei di gran lunga mantenere il codice fornito rispetto alla maggior parte delle "risposte".
Peilonrayz,

Risposte:


47

Non sono sicuro che l'ordine sia importante, ma potresti creare una tupla e scompattarla in una lista di comprensione.

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

53

Crea una funzione piacevole e leggibile:

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

Uso:

list(range_with_negatives(6, 10))

Ecco come ottenere una comoda fodera per qualsiasi cosa. Evita di provare a sembrare un hacker di magia professionale.


21
Il pregiudizio contro la comprensione di elenchi / dict è abbastanza comune su SO, ed è di solito giustificato dicendo che non sono "leggibili". La leggibilità intrinseca di un costrutto (piuttosto che come viene usata) è in gran parte soggettiva. Personalmente trovo che le comprensioni siano più leggibili, e questa soluzione molto meno (confronta: "annota la pressione sanguigna di ogni maschio oltre i 50" rispetto a "Passa attraverso ogni persona. OK, sono maschi? Se sì, allora ..." ). Li uso per questo motivo, non perché sto cercando di "sembrare" qualsiasi cosa. Le lunghe comprensioni possono essere interrotte su più righe se questo è il problema.
gennaio

2
Giusto. Ma è qui che la soggettività entra in gioco: io personalmente faccio trovare la soluzione lista comp almeno altrettanto leggibili, in termini di sforzo mentale, come la funzione di generatore. Vorrei comunque migliorare la leggibilità (o meglio, la leggibilità per me) della risposta di Datanovice rinominando yin signedValue. Per me il vero valore aggiunto dell'approccio di definire una funzione sarebbe quello di fornire risultati nell'ordine rigoroso richiesto dalla domanda (positiva, quindi negativa) evitando il problema del chainone-liner di Barmar in cui argomenti numerici leggermente diversi hanno essere codificato due volte.
gennaio

2
Concordo con il sentimento generale sulla comprensione dell'elenco, ma definire una funzione come questa è anche una tecnica molto utile da conoscere. (È particolarmente potente raccogliere risultati da un algoritmo ricorsivo scrivendo un generatore ricorsivo e passando il risultato di livello superiore a listqualsiasi cosa.) Dopo anni di tentativi di codificare il modo migliore per prendere queste decisioni, l'unico principio generale che ha senso per me : se c'è un nome ovvio e buono da dare a qualcosa nel programma, cogli l'occasione per farlo (e le funzioni sono uno dei modi in cui lo facciamo).
Karl Knechtel,

44

Direi che la soluzione più semplice è decomprimere due intervalli in un elenco utilizzando l' *operatore di decompressione:

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

Non solo questa è la risposta più breve ancora proposta, è anche la più performante, perché costruisce solo un singolo elenco e non comporta chiamate di funzioni oltre le due ranges.

Ho verificato questo testando tutte le risposte a questa domanda utilizzando il timeitmodulo:

ID risposta Metodo timeit risultato
-------------------------------------------------- ------------------------------------------------
(in questione) [x per x nell'intervallo (6,10)] + [y per y nell'intervallo (-9, -5)] 0,843 usec per loop
(questa risposta) [* range (6, 10), * range (-9, -5)] 0,509 usec per loop
61348876 [y per x in range (6,10) per y in (x, -x)] 0,754 usec per loop
61349149 list (range_with_negatives (6, 10)) 0,795 usec per loop
61348914 list (itertools.chain (range (6, 10), range (-9, -5))) 0.709 usec per loop
61366995 [segno * x per segno, x in itertools.product ((- 1, 1), intervallo (6, 10))] 0,899 usec per loop
61371302 elenco (intervallo (6, 10)) + elenco (intervallo (-9, -5)) 0,729 usec per loop
61367180 list (range_with_negs (6, 10)) 1,95 usec per loop

(test di timeit eseguiti con Python 3.6.9 sul mio computer (specifiche medie))


2
Non sono troppo entusiasta di presumere che le prestazioni siano rilevanti quando tutto ciò che hai è un esempio con <10 articoli, ma questa è chiaramente la soluzione più semplice.
JollyJoker

Come riesci a capire l'inizio e la fine dei valori negativi senza codificarli?
aldokkani

2
@aldokkani[*range(x, y), *range(-y + 1, -x + 1)]
RoadrunnerWMC

21

È possibile utilizzare itertools.chain()per concatenare i due intervalli.

import itertools
list(itertools.chain(range(6, 10), range(-9, -5)))

1
Vorrei usare 'range (-6, -10, -1)' per chiarezza dell'ordine non importa (e sostituire il 6 e il 10 con variabili)
Maarten Fabré,

8

È possibile utilizzare itertools.product, che è il prodotto cartesiano.

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

Questo potrebbe essere più lento perché usi la moltiplicazione, ma dovrebbe essere facile da leggere.

Se desideri una soluzione puramente funzionale, puoi anche importare itertools.starmape operator.mul:

from itertools import product, starmap
from operator import mul

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

Tuttavia, questo è meno leggibile.


6
Trovo l'uso di product, starmape opertaor.mulinutilmente ottuso rispetto alla comprensione dell'elenco nidificato, ma approvo il suggerimento di utilizzare la moltiplicazione. [x * sign for sign in (1, -1) for x in range(6, 10)]è solo il 10% più lento di [y for x in range(6, 10) for y in (x, -x)], e nei casi in cui l'ordine conta, più di 3 volte più veloce dell'ordinamento basato sulla tupla.
ApproachingDarknessFish

Questa è un'idea chiara, ma forse un po 'troppo specifica per i dettagli del problema. Le tecniche offerte da altri si applicano più in generale.
Karl Knechtel,

@ApproachingDarknessFish Sono d'accordo starmape muldovrebbe essere evitato, ma penso productche lo renda più leggibile, poiché raggruppa gli iteratori e i loro elementi separatamente. I doppi loop nella comprensione dell'elenco possono anche essere fonte di confusione, a causa del loro ordine inaspettato.
Frank Vel,

5

Sei molto vicino, con la combinazione di due rangeoggetti. Ma c'è un modo più semplice per farlo:

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

Cioè, converti ciascun rangeoggetto in un elenco, quindi concatena i due elenchi.

Un altro approccio, usando itertools:

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

itertools.chain()è come un generalizzato +: invece di aggiungere due liste, incatena un iteratore dopo l'altro per creare un "super iteratore". Quindi passalo a list()e otterrai un elenco concreto, con tutti i numeri che desideri in memoria.


4
Questa risposta è preziosa semplicemente per l'intuizione che [x for x in ...]è meglio scritta list(...).
Karl Knechtel,

3

Pesando con l'ennesima possibilità.

Se vuoi la leggibilità, il tuo one-liner originale era piuttosto buono, ma cambierei gli intervalli in modo che siano uguali a quanto penso che i limiti negativi rendano le cose meno chiare.

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

3

IMO l'approccio utilizzato itertools.chainpresentato in un paio di altre risposte è sicuramente il più pulito di quelli forniti finora.

Tuttavia, poiché nel tuo caso l'ordine dei valori non ha importanza , puoi evitare di definire due rangeoggetti espliciti e quindi evitare di fare tutti i calcoli off-by-one necessari per l' rangeindicizzazione negativa , usando 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]

Un po 'prolisso, ma abbastanza leggibile.

Un'altra opzione simile è usare il disimballaggio di tuple / argomento con plain chain:

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

Più conciso, ma trovo più difficile disimballare la tupla da ingannare in una scansione veloce.


3

Questa è una variazione su un tema (vedi la risposta di @Derte Trdelnik ) seguendo la filosofia di doveitertools

i blocchi di costruzione dell'iteratore [...] sono utili da soli o in combinazione.

L'idea è che, mentre stiamo definendo una nuova funzione, potremmo anche renderla generica:

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

e applicalo a un particolare rangeiteratore:

list(interleaved_negatives(range(6, 10)))

1

Se vuoi mantenere l'ordine che hai specificato, puoi usare il generatore di range integrato di Python con un condizionale:

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

Che ti dà l'output:

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

E funziona anche con 0 come inizio per l'intera gamma.


2
Questo è molto inefficiente per grandi valori di start.
Solomon Ucko il

1

Ci possono essere diversi modi per portare a termine il lavoro.

Variabili fornite: 1. start = 6 2. stop = 10

Puoi provare anche questo, per un approccio diverso:

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)

-1

Proprio come le simmetrie.

a = 6 b = 10

nums = [x + y per x in (- (a + b-1), 0) per y nell'intervallo (a, b)]

Il risultato dovrebbe essere -9, -8, ..., 8, 9.

Credo che l'espressione numerica possa essere migliorata, ciò che segue "in" e "range" mi sembra ancora squilibrato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.