Comprensione dell'elenco: restituzione di due (o più) elementi per ogni elemento


92

È possibile restituire 2 (o più) elementi per ogni elemento in una comprensione dell'elenco?

Quello che voglio (esempio):

[f(x), g(x) for x in range(n)]

dovrebbe tornare [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Quindi, qualcosa per sostituire questo blocco di codice:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))

3
Per curiosità, perché vuoi farlo? Potrebbe esserci un modo migliore per raggiungere il tuo obiettivo finale senza cercare di farlo in questo modo.
murgatroid99

4
Principalmente perché mi piace la programmazione funzionale. Voglio mappare un elenco di coordinate su una tupla di coordinate dello schermo da utilizzare con la funzione pyglet.graphics.draw.
Hashmush

Risposte:


54
>>> from itertools import chain
>>> f = lambda x: x + 2
>>> g = lambda x: x ** 2
>>> list(chain.from_iterable((f(x), g(x)) for x in range(3)))
[2, 0, 3, 1, 4, 4]

Tempistiche:

from timeit import timeit

f = lambda x: x + 2
g = lambda x: x ** 2

def fg(x):
    yield f(x)
    yield g(x)

print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='list(chain.from_iterable(fg(x) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='[func(x) for x in range(3) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2')


print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='list(chain.from_iterable(fg(x) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='[func(x) for x in xrange(10**6) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

2.69210777094

3.13900787874

1.62461071932

25.5944058287

29.2623711793

25.7211849286


4
Questo codice crea tuple non necessarie (f(x), g(x)). Potrebbe essere meglio scritto come: def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
khachik

1
Potresti anche generalizzarlo con chain.from_iterable((func(x) for func in funcs) for x in range(n))). Il che, incidentalmente, eliminerebbe la lamentela di Khachik. (Anche se in un certo senso, il mio e il suo sono essenzialmente gli stessi in termini di processo. Definiamo semplicemente il generatore interno in modo diverso.)
JAB

Questo è meglio della mia sum(..., [])risposta perché non richiede di ricreare l'elenco su ogni + (quindi ha prestazioni O (N) piuttosto che prestazioni O (N ^ 2)). Lo userò ancora sum(..., [])quando voglio una battuta veloce o ho fretta, o quando il numero di termini da combinare è limitato (ad esempio <= 10).
ninjagecko

@khachik Penso che sarebbe più veloce, ma ora cronometrerò entrambi i metodi, le tuple vengono generate molto velocemente in Python.
jamylak

3
Una terza risposta, che è scomparsa, assomiglia a questa: [y for x in range(n) for y in (f(x), g(x))]ma probabilmente è più lenta. @jamylak Potresti provare anche questo se vuoi.
Hashmush

122

Comprensione doppia lista:

[f(x) for x in range(5) for f in (f1,f2)]

Demo:

>>> f1 = lambda x: x
>>> f2 = lambda x: 10*x

>>> [f(x) for x in range(5) for f in (f1,f2)]
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]

12
Questo è bello perché mostra che le composizioni a doppia lista non sono così spaventose: sono semplicemente annidate per i cicli scritti proprio come i cicli for . for x in range(5): for f in (f1, f2): newlist.append(f(x)). Li trovavo un po 'confusi perché continuavo a cercare di invertire l'ordine.
DSM

2
Questa dovrebbe essere la risposta accettata, grazie, fantastico!
Wingjam

@DSM penso, sarà fonte di confusione per sempre.)
Winand

11
sum( ([f(x),g(x)] for x in range(n)), [] )

Questo è equivalente a [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

Puoi anche pensarlo come:

def flatten(list):
    ...

flatten( [f(x),g(x)] for x in ... )

nota: il modo giusto è usare itertools.chain.from_iterableo la comprensione della doppia lista. (Non è necessario ricreare l'elenco su ogni +, quindi ha prestazioni O (N) piuttosto che prestazioni O (N ^ 2).) Lo userò comunque sum(..., [])quando voglio una battuta veloce o ho fretta , o quando il numero di termini da combinare è limitato (ad esempio <= 10). Questo è il motivo per cui ne parlo ancora qui, con questo avvertimento. Puoi anche usare le tuple: ((f(x),g(x)) for ...), ()(o per il commento di khachik, avendo un generatore fg (x) che produce una doppia tupla).


@ArashThr: sta facendo[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...
ninjagecko

Puoi spiegare cosa sta facendo esattamente?
Rsh

Nota: questo ha un runtime O (N ^ 2), quindi potrebbe essere lento su elenchi enormi.
jamylak

1
@jamylak: sì, l'ho menzionato anche nella tua risposta nei commenti. =)
ninjagecko

Considero abusare sum()in questo modo un antipattern e non vedo alcuna giustificazione per usarlo in qualsiasi circostanza. Il codice nell'altra risposta è meno digitato, quindi anche la scusa "quando voglio una battuta veloce o ho fretta" non lo taglia.
Sven Marnach

2

Questa funzione lambda comprime due elenchi in uno unico:

zipped = lambda L1, L2: [L[i] 
                         for i in range(min(len(L1), len(L2))) 
                         for L in (L1, L2)]

Esempio:

>>> f = [x for x in range(5)]
>>> g = [x*10 for x in range(5)]
>>> zipped(f, g)
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]

2

So che OP sta cercando una soluzione per la comprensione degli elenchi, ma vorrei offrire un'alternativa utilizzando list.extend().

f = lambda x: x
g = lambda x: 10*x

result = []
extend = result.extend
for x in range(5):
    extend((f(x),g(x)))

che è leggermente più veloce rispetto all'utilizzo della comprensione della doppia lista.

nums = range(100000)

def double_comprehension():
    return [func(x) for x in nums for func in (f,g)]

def list_extend():
    result = []
    extend = result.extend
    for x in nums:
        extend((f(x),g(x)))
    return result

%timeit -n100 double_comprehension()
23.4 ms ± 67 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit -n100 list_extend()
20.5 ms ± 213 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Versione Python: 3.8.0


0

Una soluzione che utilizza ridurre :

from functools import reduce

f    = lambda x: f"f({x})" ## Just for example
g    = lambda x: f"g({x})"
data = [1, 2, 3]

reduce(lambda acc, x: acc + [f(x), g(x)], data, [])
# => ['f(1)', 'g(1)', 'f(2)', 'g(2)', 'f(3)', 'g(3)']

Sebbene non sia una comprensione di elenchi, questo è un modo funzionale per affrontare il problema. La comprensione di una lista è essenzialmente un altro modo di mapinterpretare i dati, ma in questo caso in cui la mappatura non è uno a uno tra l'input e l'output, reducelascia un po 'di spazio su come generare l'output.

In generale, qualsiasi forimplementazione del modulo:

result = []
for n in some_data:
  result += some_operation()
  ## etc.

(Cioè per i cicli destinati a produrre un effetto collaterale su un elenco o una struttura dati simile)

Può essere sottoposto a refactoring in map/reduce/filterun'implementazione dichiarativa .

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.