2013-03-20 8 views
5

C'è un modo per produrre due (o più) articoli per iterazione in una lista/dizionario/set di comprensione? Come semplice esempio, per emettere tutti i doppi positivi e negativi dei numeri interi da 1 a 3 (ovvero, {x | x = ±2n, n ∈ {1...3}}), esiste una sintassi simile alla seguente?Comprensioni: più valori per iterazione

>>> [2*i, -2*i for i in range(1, 4)] 
[2, -2, 4, -4, 6, -6] 

So che potrei tuple di uscita di (+i,-i) e appiattire che, ma mi chiedevo se ci fosse un modo per risolvere completamente il problema utilizzando un unico comprensione.

Attualmente, sto producendo due liste e concatenare (che funziona, a condizione che l'ordine non è importante):

>>> [2*i for i in range(1, 4)] + [-2*i for i in range(1, 4)] 
[2, 4, 6, -2, -4, -6] 
+0

Si noti che se l'ordine non è importante, probabilmente si sta utilizzando la struttura sbagliata dei dati - '{2 * i per i in range (1, 4) } | {-2 * i per i in range (1, 4)} '. –

+1

@Lattyware Questo ha l'effetto collaterale di unificare il pool di elementi di output, che può essere o non essere desiderabile. –

+0

Quindi * probabilmente * - se hai bisogno di duplicati, sì, i set non sono adatti. –

risposta

5

Un'altra forma di comprensione nidificato:

>>> [sub for i in range(1, 4) for sub in (2*i, -2*i)] 
[2, -2, 4, -4, 6, -6] 
4

La risposta migliore è di usare semplicemente itertools.chain.from_iterable() a, come si parla, appiattire la lista:

itertools.chain.from_iterable((2*i, -2*i) for i in range(1, 4)) 

Questo è abbastanza leggibile, e non richiede l'iterazione sulla fonte per due volte (che può essere problematico dato alcuni iteratori possono essere esauriti, e significa sforzo supplementare computazionale).

+0

Grazie. Questa è una buona soluzione, ma in genere evito di importare roba a meno che non sia necessario. Dovrò aggiungere itertools al mio arsenale ad un certo punto però, quindi +1 –

+0

Non c'è alcun motivo per reinventare la ruota quando qualcosa esiste nella libreria standard - non c'è motivo di temere l'importazione. 'itertools' è una grande parte di Python (basta controllare quante risposte di SO Python lo usano). È veloce, efficiente e fa un sacco di cose così non devi. –

4

Anche se vorrei utilizzare il metodo itertools @Lattyware suggerito, ecco un approccio più generale che utilizza un generatore che potrebbe anche essere utile.

>>> def nums(): 
     for i in range(1, 4): 
      yield 2*i 
      yield -2*i 


>>> list(nums()) 
[2, -2, 4, -4, 6, -6] 
+1

+1, è un buon modo per farlo, i generatori tendono anche ad essere sorprendentemente veloci. –

+1

@Lattyware Vorrei fare +1 su di voi, ma ho esaurito il numero – jamylak

+0

XD, in piena giornata upvoting? –

6

Un'altra opzione è la comprensione nidificato:

r = [2*i*s for i in range(1, 4) for s in 1, -1] 

Per un caso più generale:

r = [item for tpl in (<something that yields tuples>) for item in tpl] 

con il tuo esempio originale:

r = [item for tpl in ((2*i, -2*i) for i in range(1, 4)) for item in tpl] 

anche se mi piacerebbe suggerisco davvero itertools.chain.from_iterable come ha detto @Lattyware.

+0

Questo è davvero elegante. –

+0

Si noti che questo ha generalmente prestazioni peggiori che si appiattiscono usando 'itertools' ed è generalmente molto difficile da leggere (le comprensioni delle liste annidate sono generalmente aborrite dai neofiti a causa della mancanza di un ordine ovvio da leggere). –

+0

@Lattyware Potresti approfondire il motivo per cui questo avrebbe avuto prestazioni peggiori? –

1

Secondo PEP202 non c'è modo di uscita più di un oggetto da una lista di comprensione:

- La forma [x, y for ...] è annullato; è necessario scrivere [(x, y) for ...].

+0

Questo è vero solo in 3.x +, IIRC. In 2.x, è possibile utilizzare il precedente metodo per costruire una tupla, ma in 3.x, è stato modificato per corrispondere alle espressioni del generatore in cui le parentesi erano necessarie per risolvere un'ambiguità nella sintassi (come in 3.x, lista comps ed i loro fratelli sono solo zucchero sintattico attorno alle espressioni del generatore). –

+0

Giusto per essere chiari - in entrambe le versioni, si ottiene solo una tupla, non più elementi nell'elenco. –

+0

Non risolve il problema, ma questa è una buona informazione. – nobar