Zrozumienie listy: zwracanie dwóch (lub więcej) elementów dla każdego elementu


89

Czy jest możliwe zwrócenie 2 (lub więcej) pozycji dla każdej pozycji w zrozumieniu listy?

Czego chcę (przykład):

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

powinien wrócić [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Więc coś, co zastąpi ten blok kodu:

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

3
Z ciekawości, dlaczego chcesz to zrobić? Może istnieć lepszy sposób na osiągnięcie celu końcowego bez próbowania tego w ten sposób.
murgatroid99

3
Głównie dlatego, że lubię programowanie funkcjonalne. Chcę zmapować listę współrzędnych do krotki współrzędnych ekranu do użycia z funkcją pyglet.graphics.draw.
Hashmush

Odpowiedzi:


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]

Czasy:

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
Ten kod tworzy niepotrzebne krotki (f(x), g(x)). Mogłoby być lepiej zapisać jako: def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
khachik

1
Można to nawet uogólnić za pomocą chain.from_iterable((func(x) for func in funcs) for x in range(n))). Co, nawiasem mówiąc, wyeliminowałoby skargę chaczika. (Chociaż w pewnym sensie moje i jego są zasadniczo takie same pod względem procesu. Po prostu inaczej definiujemy wewnętrzny generator.)
JAB

Jest to lepsze niż moja sum(..., [])odpowiedź, ponieważ nie wymaga ponownego tworzenia listy na każdym + (w ten sposób ma wydajność O (N) zamiast wydajności O (N ^ 2)). Nadal będę używał, sum(..., [])gdy chcę szybkiego, jednoliniowego tekstu, spieszy mi się lub gdy liczba łączonych terminów jest ograniczona (np. <= 10).
ninjagecko

@khachik Myślę, że byłoby to szybsze, ale zmierzę teraz czas dla obu metod, ale krotki są generowane bardzo szybko w Pythonie.
jamylak

3
Trzecia odpowiedź, która zniknęła, wyglądała tak: [y for x in range(n) for y in (f(x), g(x))]Ale to prawdopodobnie wolniej. @jamylak Możesz to też przetestować, jeśli chcesz.
Hashmush

118

Rozumienie podwójnej listy:

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

Próbny:

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

10
Jest to fajne, ponieważ pokazuje, że kompilacje z podwójną listą nie są tak przerażające: są po prostu zagnieżdżone dla pętli napisanych tak jak pętle for . for x in range(5): for f in (f1, f2): newlist.append(f(x)). Kiedyś uważałem je za trochę zagmatwane, ponieważ ciągle próbowałem odwrócić kolejność.
DSM

1
To powinna być zaakceptowana odpowiedź, dziękuję, niesamowita!
Wingjam

@DSM myślę, że to będzie zagmatwane na zawsze.)
Winand

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

Jest to równoważne z [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

Możesz również myśleć o tym jako:

def flatten(list):
    ...

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

Uwaga: Właściwym sposobem jest użycie itertools.chain.from_iterablelub podwójne rozumienie listy. (To nie wymaga odtworzenia listy na każdy +, co ma O (n) wydajności zamiast O (n ^ 2) wydajność). Będę nadal korzystać sum(..., []), gdy chcę szybko jedną wkładkę lub jestem w pośpiechu lub gdy liczba łączonych terminów jest ograniczona (np. <= 10). Dlatego wciąż o tym tutaj wspominam, z tym zastrzeżeniem. Możesz także użyć krotek: ((f(x),g(x)) for ...), ()(lub za komentarzem khaczika, mając generator fg (x), który daje dwie krotki).


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

Czy możesz wyjaśnić, co dokładnie robi?
Rsh

Uwaga: to ma środowisko wykonawcze O (N ^ 2), więc może być powolne w przypadku dużych list.
jamylak

1
@jamylak: tak, wspomniałem o tym również w Twojej odpowiedzi w komentarzach. =)
ninjagecko

Uważam, że nadużycie sum()w ten sposób jest antywzorem i nie widzę żadnego uzasadnienia, aby go używać w jakichkolwiek okolicznościach. Kod w Twojej drugiej odpowiedzi to mniej wpisywania, więc nawet wymówka „kiedy chcę szybkiej jednej linijki lub spieszę się” tak naprawdę nie wystarcza.
Sven Marnach

2

Ta funkcja lambda zamyka dwie listy w jedną:

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

Przykład:

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

Wiem, że OP szuka rozwiązania do rozumienia listy, ale chciałbym zaoferować alternatywne użycie 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)))

co jest nieznacznie szybsze niż używanie podwójnego rozumienia list.

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)

Wersja Pythona: 3.8.0


0

Rozwiązanie wykorzystujące redukuj :

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)']

Chociaż nie jest to rozumienie listy, jest to funkcjonalny sposób podejścia do problemu. Zrozumienie listy jest zasadniczo innym sposobem mapna przeglądanie danych, ale w tym przypadku, gdy mapowanie nie jest jednym do jednego między wejściem a wyjściem,reduce pozwala na pewne wahanie się w jaki sposób można wygenerować dane wyjściowe.

Ogólnie rzecz biorąc, każda forimplementacja formularza:

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

(Tj. Pętle mające na celu wywołanie efektu ubocznego na liście lub podobnej strukturze danych)

Można zamienić na deklaratywną map/reduce/filterimplementację.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.