Listenverständnis: Rückgabe von zwei (oder mehr) Elementen für jedes Element

88

Ist es möglich, 2 (oder mehr) Elemente für jedes Element in einem Listenverständnis zurückzugeben?

Was ich will (Beispiel):

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

sollte zurückkehren [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Also etwas, um diesen Codeblock zu ersetzen:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))
Hashmush
quelle
2
Warum willst du das aus Neugier tun? Es gibt möglicherweise einen besseren Weg, um Ihr Endziel zu erreichen, ohne dies zu versuchen.
Murgatroid99
3
Hauptsächlich, weil ich funktionale Programmierung mag. Ich möchte eine Liste von Koordinaten einem Tupel von Bildschirmkoordinaten zuordnen, die mit der Funktion pyglet.graphics.draw verwendet werden sollen.
Hashmush

Antworten:

52
>>> 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]

Timings:

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

Jamylak
quelle
4
Dieser Code erzeugt unnötige Tupel (f(x), g(x)). Könnte besser geschrieben sein als : def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
Khachik
1
Sie könnten es sogar mit verallgemeinern chain.from_iterable((func(x) for func in funcs) for x in range(n))). Das würde übrigens Khachiks Beschwerde beseitigen. (Obwohl in gewissem Sinne meine und seine in Bezug auf den Prozess im Wesentlichen gleich sind. Wir definieren den inneren Generator einfach anders.)
JAB
Dies ist besser als meine sum(..., [])Antwort, da es nicht erforderlich ist, die Liste bei jedem + neu zu erstellen (hat also eher eine O (N) -Leistung als eine O (N ^ 2) -Leistung). Ich werde es immer noch verwenden, sum(..., [])wenn ich einen schnellen Einzeiler möchte oder es eilig habe oder wenn die Anzahl der zu kombinierenden Begriffe begrenzt ist (z. B. <= 10).
Ninjagecko
@ Khachik Ich denke, das wäre schneller, aber ich werde beide Methoden jetzt zeitlich festlegen. Tupel werden in Python allerdings sehr schnell generiert.
Jamylak
3
Eine dritte Antwort, die verschwand, sah folgendermaßen aus: [y for x in range(n) for y in (f(x), g(x))]Aber das ist wahrscheinlich langsamer. @ Jamylak Du kannst das auch testen, wenn du willst.
Hashmush
114

Doppellistenverständnis:

[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]
Ninjagecko
quelle
10
Das ist schön, weil es zeigt, dass Doppellisten-Comps nicht so beängstigend sind: Sie sind einfach für Schleifen verschachtelt, die genau wie für Schleifen geschrieben wurden . for x in range(5): for f in (f1, f2): newlist.append(f(x)). Früher fand ich sie etwas verwirrend, weil ich immer wieder versuchte, die Reihenfolge umzukehren.
DSM
1
Dies sollte die akzeptierte Antwort sein, danke, erstaunlich!
Wingjam
@DSM Ich denke, es wird für immer verwirrend sein.)
Winand
11
sum( ([f(x),g(x)] for x in range(n)), [] )

Dies entspricht [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

Sie können sich das auch so vorstellen:

def flatten(list):
    ...

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

Hinweis: Der richtige Weg ist die Verwendung itertools.chain.from_iterableoder das doppelte Listenverständnis. (Es ist nicht erforderlich, die Liste bei jedem + neu zu erstellen, hat also eher eine O (N) -Leistung als eine O (N ^ 2) -Leistung.) Ich werde sie immer noch verwenden, sum(..., [])wenn ich einen schnellen Einzeiler möchte oder es eilig habe oder wenn die Anzahl der zu kombinierenden Begriffe begrenzt ist (z. B. <= 10). Deshalb erwähne ich es hier immer noch mit dieser Einschränkung. Sie können auch Tupel verwenden: ((f(x),g(x)) for ...), ()(oder laut Khachiks Kommentar einen Generator fg (x), der ein Zwei-Tupel ergibt).

Ninjagecko
quelle
@ArashThr: es tut[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...
Ninjagecko
Können Sie erklären, was es genau tut?
Rsh
Hinweis: Dies hat eine Laufzeit von O (N ^ 2), sodass es auf großen Listen langsam sein kann.
Jamylak
1
@ Jamylak: Ja, das habe ich auch in der Antwort in den Kommentaren erwähnt. =)
Ninjagecko
Ich betrachte den Missbrauch sum()auf diese Weise als Antimuster, und ich sehe keine Rechtfertigung dafür, es unter welchen Umständen auch immer zu verwenden. Der Code in Ihrer anderen Antwort ist weniger tippend, so dass selbst die Ausrede "wenn ich einen schnellen Einzeiler möchte oder es eilig habe" es nicht wirklich schneidet.
Sven Marnach
2

Diese Lambda-Funktion komprimiert zwei Listen zu einer einzigen:

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

Beispiel:

>>> 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]
Daniel Reis
quelle
1

Ich weiß, dass OP nach einer Lösung für das Listenverständnis sucht, möchte aber eine alternative Verwendung anbieten 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)))

Das ist etwas schneller als das doppelte Listenverständnis.

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)

Python-Version: 3.8.0

Remykarem
quelle