Zastanawiam się, czy istnieje skrót do utworzenia prostej listy z listy list w Pythonie.
Mogę to zrobić w for
pętli, ale może jest jakiś fajny „jednowarstwowy”? Próbowałem z reduce()
, ale pojawia się błąd.
Kod
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)
Komunikat o błędzie
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'
Odpowiedzi:
Biorąc pod uwagę listę list
l
,flat_list = [item for sublist in l for item in sublist]
co znaczy:
jest szybszy niż dotychczas publikowane skróty. (
l
to lista do spłaszczenia).Oto odpowiednia funkcja:
Jako dowód możesz użyć
timeit
modułu ze standardowej biblioteki:Objaśnienie: skróty oparte na
+
(w tym dorozumiane użycie wsum
) są z konieczności,O(L**2)
gdy istnieją L listy podrzędne - ponieważ lista wyników pośrednich stale się wydłuża, z każdym krokiem przydzielany jest nowy obiekt listy wyników pośrednich i wszystkie elementy w poprzednim wyniku pośrednim należy skopiować (a także kilka nowych na końcu). Tak więc, dla uproszczenia i bez faktycznej utraty ogólności, powiedzmy, że masz L podlisty I elementów każdy: pierwsze I elementy są kopiowane tam iz powrotem L-1 razy, drugie I elementy L-2 i tak dalej; całkowita liczba kopii jest I razy suma x dla x od 1 do L wykluczone, tjI * (L**2)/2
.Zrozumienie listy generuje tylko jedną listę, raz i kopiuje każdy element (z pierwotnego miejsca zamieszkania do listy wyników) również dokładnie raz.
źródło
itertools.chain.from_iterable
:$ python -mtimeit -s'from itertools import chain; l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'list(chain.from_iterable(l))'
. Działa nieco ponad dwa razy szybciej niż analiza listy zagnieżdżonej, która jest najszybszą z pokazanych tu alternatyw.list()
aby zrealizować iterator na liście.list(itertools.chain.from_iterable(l))
jest najlepszy - jak zauważono w innych komentarzach i odpowiedzi Shawna.Możesz użyć
itertools.chain()
:Lub możesz użyć,
itertools.chain.from_iterable()
która nie wymaga rozpakowywania listy z*
operatorem :źródło
*
to podstępna rzecz, która sprawia, że jestchain
mniej prosta niż zrozumienie listy. Musisz wiedzieć, że łańcuch łączy tylko iterabile przekazywane jako parametry, a * powoduje, że lista najwyższego poziomu zostaje rozwinięta w parametry, więcchain
łączy wszystkie iterable, ale nie schodzi dalej. Myślę, że dzięki temu zrozumienie jest bardziej czytelne niż użycie łańcucha w tym przypadku.for
pętlę, która wielokrotnieappend
jest bardziej oczywista.list = [["abc","bcd"],["cde","def"],"efg"]
spowoduje wyjście["abc", "bcd", "cde", "def", "e", "f", "g"].
Uwaga autora : Jest to nieefektywne. Ale zabawne, bo monoidy są niesamowite. Nie jest odpowiedni do produkcyjnego kodu Python.
To po prostu sumuje elementy iterowalności przekazane w pierwszym argumencie, traktując drugi argument jako wartość początkową sumy (jeśli nie podano,
0
zamiast tego zostanie użyty i ten przypadek da ci błąd).Ponieważ sumujesz zagnieżdżone listy, tak naprawdę otrzymujesz
[1,3]+[2,4]
wyniksum([[1,3],[2,4]],[])
równy[1,3,2,4]
.Pamiętaj, że działa tylko na listach list. W przypadku list list list potrzebne będzie inne rozwiązanie.
źródło
Monoid
, która jest jedną z najwygodniejszych abstrakcji dla myślenia o+
operacji w ogólnym znaczeniu (nie ogranicza się tylko do liczb). Ta odpowiedź zasługuje na +1 ode mnie za (prawidłowe) traktowanie list jako monoidu. Przedstawienie dotyczy jednak ...Testowałem większość proponowanych rozwiązań za pomocą perfplot ( mojego projektu dla zwierząt domowych, zasadniczo opakowania
timeit
) i znalazłembyć najszybszym rozwiązaniem, zarówno w przypadku łączenia wielu małych list, jak i kilku długich list. (
operator.iadd
jest równie szybki.)Kod do odtworzenia fabuły:
źródło
extend()
Metoda w Twojej przykład modyfikujex
zamiast wrócić użyteczną wartość (którareduce()
oczekuje).Szybszym sposobem byłoby zrobienie
reduce
wersjiźródło
reduce(operator.add, l)
byłby prawidłowym sposobem wykonaniareduce
wersji. Wbudowane są szybsze niż lambdas.timeit.timeit('reduce(operator.add, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000)
0,017956018447875977 *timeit.timeit('reduce(lambda x, y: x+y, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000)
0,025218963623046875integers
. Ale co jeśli lista zawierastring
?operator.add
Funkcja działa równie dobrze dla obu list liczb całkowitych i list ciągów znaków.Nie wymyślaj ponownie koła, jeśli używasz Django :
... Pandy :
... Itertools :
... Matplotlib
... Unipath :
... Narzędzia konfiguracji :
źródło
flatten = itertools.chain.from_iterable
powinna być poprawna odpowiedźlist_of_menuitems = [1, 2, [3, [4, 5, [6]]]]
, spowoduje to na:[1, 2, 3, 4, 5, 6]
. Tęsknię za poziomem spłaszczenia.Oto ogólne podejście, które stosuje się do liczb , ciągów , zagnieżdżonych list i mieszanych kontenerów.
Kod
Uwagi :
yield from flatten(x)
można zamienićfor sub_x in flatten(x): yield sub_x
collection.abc
dotyping
modułu.Próbny
Odniesienie
źródło
more_itertools
m.in. o dyskusji w tym poście. Twoje zdrowie.traverse
może to być również dobre imię dla tego sposobu drzewa, podczas gdy trzymałbym go mniej uniwersalnym dla tej odpowiedzi, trzymając się zagnieżdżonych list.if hasattr(x, '__iter__')
zamiast importować / sprawdzać względemIterable
, co również wyklucza łańcuchy.Jeśli chcesz spłaszczyć strukturę danych, w której nie wiesz, jak głęboko jest zagnieżdżona, możesz użyć 1
iteration_utilities.deepflatten
Jest to generator, więc musisz rzucić wynik na
list
lub jawnie iterować nad nim.Aby spłaszczyć tylko jeden poziom i jeśli każdy z elementów jest iterowalny, możesz również użyć tego,
iteration_utilities.flatten
który sam jest cienkim opakowaniemitertools.chain.from_iterable
:Wystarczy dodać kilka czasów (na podstawie odpowiedzi Nico Schlömera, która nie zawierała funkcji przedstawionej w tej odpowiedzi):
Jest to wykres dziennika, który uwzględnia szeroki zakres wartości. Dla rozumowania jakościowego: niższe jest lepsze.
Wyniki pokazują, że jeśli iterable zawiera tylko kilka wewnętrznych iterables następnie
sum
będzie najszybszy, jednak przez długi iterables tylkoitertools.chain.from_iterable
,iteration_utilities.deepflatten
czy zagnieżdżonego pojmowania mieć wystarczającą wydajność przyitertools.chain.from_iterable
czym najszybciej (jak już zauważył Nico Schlömer).1 Oświadczenie: Jestem autorem tej biblioteki
źródło
sum
nie działa już na dowolnych sekwencjach od samego początku0
, czyniącfunctools.reduce(operator.add, sequences)
zamianę (czy nie cieszymy się, że usunęli jereduce
z wbudowanych?) Gdy znane są typy, użycie może być szybszetype.__add__
.sum
. Czy wiesz, w których wersjach języka Python przestał działać?0
jest tylko domyślną wartością początkową, więc działa, jeśli użyje się argumentu początkowego, aby rozpocząć od pustej listy ... ale nadal specjalne ciągi znaków i każą mi użyć złączenia. Implementujefoldl
zamiastfoldl1
. Ten sam problem pojawia się w 2.7.Cofam moje oświadczenie. suma nie jest zwycięzcą. Chociaż jest krótszy, gdy lista jest mała. Ale wydajność spada znacznie przy większych listach.
Wersja sumy nadal działa przez ponad minutę i nie została jeszcze przetworzona!
W przypadku średnich list:
Korzystanie z małych list i timeit: number = 1000000
źródło
Wydaje się, że jest to zamieszanie
operator.add
! Gdy dodasz dwie listy razem, poprawnym terminem jestconcat
nie dodawanie.operator.concat
jest to, czego potrzebujesz.Jeśli myślisz o funkcjonalności, jest to tak proste:
Widzisz, że redukcja respektuje typ sekwencji, więc kiedy dostarczysz krotkę, otrzymasz krotkę. Spróbujmy z listą:
Aha, odzyskałeś listę.
Jak o wydajności ::
from_iterable
jest dość szybki! Ale nie można tego porównywaćconcat
.źródło
list(chain.from_iterable(...))
i 2,5 sekundy dlareduce(concat, ...)
. Problem polega na tym, żereduce(concat, ...)
ma kwadratowy czas działania, podczas gdychain
jest liniowy.Dlaczego korzystasz z rozszerzenia?
To powinno działać dobrze.
źródło
from functools import reduce
Rozważ zainstalowanie
more_itertools
pakietu.Jest dostarczany z implementacją
flatten
( źródło , z przepisów itertools ):Począwszy od wersji 2.4, można spłaszczyć bardziej skomplikowane, zagnieżdżone iteracje
more_itertools.collapse
( źródło , napisane przez abarnet).źródło
Powodem, dla którego twoja funkcja nie działała, jest to, że rozszerzenie rozszerza tablicę w miejscu i nie zwraca jej. Nadal możesz zwrócić x z lambda, używając czegoś takiego:
Uwaga: rozszerzenie jest bardziej wydajne niż + na listach.
źródło
extend
jest lepiej wykorzystywane jakonewlist = []
,extend = newlist.extend
,for sublist in l: extend(l)
gdyż unika (dość duże) narzutlambda
, odnośnika atrybutx
, aor
.from functools import reduce
źródło
def flatten(l, a=None): if a is None: a = []
[...]Wersja rekurencyjna
źródło
matplotlib.cbook.flatten()
będzie działać dla zagnieżdżonych list, nawet jeśli zagnieżdżą się one głębiej niż w przykładzie.Wynik:
Jest to 18x szybciej niż podkreślenie ._. Spłaszczyć:
źródło
Przyjęta odpowiedź nie działała dla mnie w przypadku tekstowych list o zmiennej długości. Oto alternatywne podejście, które zadziałało dla mnie.
Zaakceptowana odpowiedź, która nie zadziałała:
Nowe proponowane rozwiązania, które zrobił dla mnie pracować:
źródło
Złą cechą powyższej funkcji Anila jest to, że wymaga ona od użytkownika, aby zawsze ręcznie określał drugi argument jako pustą listę
[]
. Zamiast tego powinno to być ustawienie domyślne. Ze względu na sposób działania obiektów Python należy je ustawić wewnątrz funkcji, a nie w argumentach.Oto działająca funkcja:
Testowanie:
źródło
Najprostsze wydają mi się:
źródło
Można również skorzystać z mieszkania NumPy :
Edytuj 02.02.2016: Działa tylko wtedy, gdy listy podrzędne mają identyczne wymiary.
źródło
Możesz użyć numpy:
flat_list = list(np.concatenate(list_of_list))
źródło
[1, 2, [3], [[4]], [5, [6]]]
Jeśli chcesz zrezygnować z niewielkiej prędkości, aby uzyskać czystszy wygląd, możesz użyć
numpy.concatenate().tolist()
lubnumpy.concatenate().ravel().tolist()
:Możesz dowiedzieć się więcej tutaj w dokumentach numpy.concatenate i numpy.ravel
źródło
[1, 2, [3], [[4]], [5, [6]]]
Najszybsze rozwiązanie, jakie znalazłem (dla dużej listy i tak):
Gotowy! Możesz oczywiście zmienić go z powrotem w listę, wykonując listę (l)
źródło
Prosty kod dla
underscore.py
wentylatora pakietuRozwiązuje wszystkie problemy spłaszczania (brak elementu listy lub złożone zagnieżdżenie)
Możesz zainstalować za
underscore.py
pomocą pipźródło
źródło
[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
Uwaga : poniżej dotyczy Python 3.3+, ponieważ używa
yield_from
.six
jest także pakietem innej firmy, choć jest stabilny. Alternatywnie możesz użyćsys.version
.W przypadku
obj = [[1, 2,], [3, 4], [5, 6]]
wszystkich rozwiązań tutaj są dobre, w tym ze zrozumieniem listy iitertools.chain.from_iterable
.Rozważ jednak ten nieco bardziej złożony przypadek:
Tutaj jest kilka problemów:
6
to tylko skalar; nie jest iterowalny, więc powyższe trasy zawiodą tutaj.'abc'
, jest technicznie iterable (wszystkiestr
s są). Jednak czytając trochę między wierszami, nie chcesz traktować go jako takiego - chcesz traktować go jako pojedynczy element.[8, [9, 10]]
sam w sobie jest iterowalnym zagnieżdżonym. Podstawowe rozumienie listy ichain.from_iterable
wyodrębnianie tylko „1 poziom w dół”.Możesz temu zaradzić w następujący sposób:
Tutaj sprawdzasz, czy podelement (1) jest iterowalny
Iterable
, z ABCitertools
, ale też chcesz upewnić się, że (2) element nie jest „ciągiem”.źródło
yield from
nafor
pętlę, np.for x in flatten(i): yield x
Ten kod działa również dobrze, ponieważ po prostu rozszerza listę do końca. Chociaż jest bardzo podobny, ale ma tylko jeden dla pętli. Ma więc mniej złożoności niż dodanie 2 dla pętli.
źródło
Zaletą tego rozwiązania w porównaniu z większością innych tutaj jest to, że jeśli masz listę jak:
podczas gdy większość innych rozwiązań generuje błąd, to rozwiązanie je obsługuje.
źródło
To może nie być najskuteczniejszy sposób, ale pomyślałem o umieszczeniu jednej linijki (w rzeczywistości dwuwarstwowej). Obie wersje będą działać na dowolnych listach zagnieżdżonych w hierarchii, wykorzystując funkcje językowe (Python3.5) i rekurencję.
Dane wyjściowe to
Działa to w pierwszej kolejności. Rekurencja spada w dół, aż znajdzie element inny niż lista, a następnie rozszerza zmienną lokalną,
flist
a następnie przywraca ją do elementu nadrzędnego. Ilekroćflist
jest zwracany, jest on rozszerzony na listę rodzicówflist
w rozumieniu listy. Dlatego w katalogu głównym zwracana jest płaska lista.Powyższy tworzy kilka list lokalnych i zwraca je, które służą do rozszerzenia listy rodziców. Myślę, że rozwiązaniem tego może być tworzenie gloabl
flist
, jak poniżej.Wyjście jest znowu
Chociaż w tej chwili nie jestem pewien co do wydajności.
źródło
Kolejne niezwykłe podejście, które działa dla hetero- i jednorodnych list liczb całkowitych:
źródło
wierd_list = [[1, 2, 3], [4, 5, 6], [7], [8, 9], 10]
>>nice_list=[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 0]
flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
[int(e.strip('[ ]')) for e in str(deep_list).split(',')]
. Ale proponuję pozostać przy propozycji Deleet dotyczącej rzeczywistych przypadków użycia. Nie zawiera przeróbek typu hacky, jest szybszy i bardziej wszechstronny, ponieważ oczywiście obsługuje również listy z typami mieszanymi.