Jak zrobić płaską listę z listy?

3365

Zastanawiam się, czy istnieje skrót do utworzenia prostej listy z listy list w Pythonie.

Mogę to zrobić w forpę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'
Emma
źródło
20
Szczegółowa dyskusja na ten temat jest tutaj: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , omawiając kilka metod spłaszczania dowolnie zagnieżdżonych list list. Ciekawa lektura!
RichieHindle
6
Niektóre inne odpowiedzi są lepsze, ale przyczyną niepowodzenia jest to, że metoda „przedłużyć” zawsze zwraca Brak. W przypadku listy o długości 2 zadziała, ale zwróci Brak. W przypadku dłuższej listy zużyje pierwsze 2 argumenty, co zwraca Brak. Następnie kontynuuje z None.extend (<Third arg>), co powoduje ten błąd
mehtunguh
@ shawn-podbródek jest tutaj bardziej pythonowy, ale jeśli chcesz zachować typ sekwencji, powiedz, że masz krotkę krotek zamiast listy list, powinieneś użyć zmniejszania (operator.concat, tuple_of_tuples). Używanie operatora.concat z krotkami wydaje się działać szybciej niż chain.from_iterables z listą.
Meitham,

Odpowiedzi:

4787

Biorąc pod uwagę listę list l,

flat_list = [item for sublist in l for item in sublist]

co znaczy:

flat_list = []
for sublist in l:
    for item in sublist:
        flat_list.append(item)

jest szybszy niż dotychczas publikowane skróty. ( lto lista do spłaszczenia).

Oto odpowiednia funkcja:

flatten = lambda l: [item for sublist in l for item in sublist]

Jako dowód możesz użyć timeitmodułu ze standardowej biblioteki:

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 1.1 msec per loop

Objaśnienie: skróty oparte na +(w tym dorozumiane użycie w sum) 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, tj I * (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.

Alex Martelli
źródło
486
Próbowałem testu z tymi samymi danymi, używając 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.
intuicyjnie 15.10.10
274
Znalazłem składnię trudną do zrozumienia, dopóki nie zdałem sobie sprawy, że możesz myśleć o niej dokładnie tak, jak zagnieżdżona dla pętli. dla podlisty wl: dla przedmiotów z podlisty: wydaj przedmiot
Rob Crowell
23
@BorisChervenkov: Zauważ, że zawarłem połączenie, list()aby zrealizować iterator na liście.
intuicyjnie
163
[liść dla drzewa w lesie dla liścia w drzewie] może być łatwiejszy do zrozumienia i zastosowania.
John Mee,
80
@Joel, tak naprawdę obecnie list(itertools.chain.from_iterable(l))jest najlepszy - jak zauważono w innych komentarzach i odpowiedzi Shawna.
Alex Martelli
1565

Możesz użyć itertools.chain():

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain(*list2d))

Lub możesz użyć, itertools.chain.from_iterable()która nie wymaga rozpakowywania listy z *operatorem :

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain.from_iterable(list2d))
Shawn Chin
źródło
13
Jest *to podstępna rzecz, która sprawia, że ​​jest chainmniej 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ęc chainłą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.
Tim Dierks
52
@TimDierks: Nie jestem pewien, czy „wymaga to zrozumienia składni Pythona” jest argumentem przeciwko używaniu danej techniki w Pythonie. Jasne, skomplikowane użycie może mylić, ale operator „splat” jest ogólnie przydatny w wielu okolicznościach i nie używa go w szczególnie niejasny sposób; odrzucenie wszystkich funkcji językowych, które niekoniecznie są oczywiste dla początkujących użytkowników, oznacza, że ​​przywiązujesz jedną rękę do tyłu. Równie dobrze może wyrzucać listy ze zrozumieniem, gdy jesteś przy nich; użytkownicy z innych środowisk znajdą forpętlę, która wielokrotnie appendjest bardziej oczywista.
ShadowRanger,
Ta odpowiedź i inne odpowiedzi tutaj dają niepoprawny wynik, jeśli najwyższy poziom zawiera również wartość. na przykład list = [["abc","bcd"],["cde","def"],"efg"]spowoduje wyjście["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr
906

Uwaga autora : Jest to nieefektywne. Ale zabawne, bo monoidy są niesamowite. Nie jest odpowiedni do produkcyjnego kodu Python.

>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

To po prostu sumuje elementy iterowalności przekazane w pierwszym argumencie, traktując drugi argument jako wartość początkową sumy (jeśli nie podano, 0zamiast tego zostanie użyty i ten przypadek da ci błąd).

Ponieważ sumujesz zagnieżdżone listy, tak naprawdę otrzymujesz [1,3]+[2,4]wynik sum([[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.

Tryptyk
źródło
99
to całkiem fajne i sprytne, ale nie użyłbym tego, ponieważ mylące jest czytanie.
andrewrk
87
Jest to algorytm Shlemiela malarza joelonsoftware.com/articles/fog0000000319.html - niepotrzebnie nieefektywny i nieprzyjemnie brzydki.
Mike Graham,
44
Dołącz operację na listach tworzy a 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 ...
ulidtko
7
@andrewrk Cóż, niektórzy uważają, że jest to najczystszy sposób: youtube.com/watch?v=IOiZatlZtGU ci, którzy nie rozumieją, dlaczego to jest fajne, muszą poczekać kilka dekad, aż wszyscy to zrobią: ) używajmy języków programowania (i abstrakcji), które zostały odkryte, a nie wynalezione, odkryto Monoid.
jhegedus
11
jest to bardzo nieefektywny sposób ze względu na kwadratowy aspekt sumy.
Jean-François Fabre
459

Testowałem większość proponowanych rozwiązań za pomocą perfplot ( mojego projektu dla zwierząt domowych, zasadniczo opakowania timeit) i znalazłem

functools.reduce(operator.iconcat, a, [])

być najszybszym rozwiązaniem, zarówno w przypadku łączenia wielu małych list, jak i kilku długich list. ( operator.iaddjest równie szybki.)

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj


Kod do odtworzenia fabuły:

import functools
import itertools
import numpy
import operator
import perfplot


def forfor(a):
    return [item for sublist in a for item in sublist]


def sum_brackets(a):
    return sum(a, [])


def functools_reduce(a):
    return functools.reduce(operator.concat, a)


def functools_reduce_iconcat(a):
    return functools.reduce(operator.iconcat, a, [])


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


def numpy_concatenate(a):
    return list(numpy.concatenate(a))


perfplot.show(
    setup=lambda n: [list(range(10))] * n,
    # setup=lambda n: [list(range(n))] * 10,
    kernels=[
        forfor,
        sum_brackets,
        functools_reduce,
        functools_reduce_iconcat,
        itertools_chain,
        numpy_flat,
        numpy_concatenate,
    ],
    n_range=[2 ** k for k in range(16)],
    xlabel="num lists (of length 10)",
    # xlabel="len lists (10 lists total)"
)
Nico Schlömer
źródło
25
W przypadku dużych zagnieżdżonych list „lista (numpy.array (a) .flat)” jest najszybszą spośród wszystkich powyższych funkcji.
Sara,
Próbowałem za pomocą wyrażenia regularnego: 'list (map (int, re.findall (r "[\ w] +", str (a)))). Prędkość jest nieco wolniejsza niż liczba numpy_concatenate
Justas
Czy istnieje sposób na wykonanie trójwymiarowego perfplotu? liczba tablic według średniego rozmiaru tablicy?
Leo
Uwielbiam twoje rozwiązanie. Krótkie, proste i wydajne :-)
ShadyMBA
181
from functools import reduce #python 3

>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(lambda x,y: x+y,l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

extend()Metoda w Twojej przykład modyfikuje xzamiast wrócić użyteczną wartość (która reduce()oczekuje).

Szybszym sposobem byłoby zrobienie reducewersji

>>> import operator
>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(operator.concat, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Greg Hewgill
źródło
19
reduce(operator.add, l)byłby prawidłowym sposobem wykonania reducewersji. Wbudowane są szybsze niż lambdas.
agf
3
@agf tutaj jest jak: * 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,025218963623046875
lukmdo
8
To jest algorytm Shlemiela malarza joelonsoftware.com/articles/fog0000000319.html
Mike Graham
2
można to wykorzystać tylko do integers. Ale co jeśli lista zawiera string?
Freddy,
3
@ Freddy: operator.addFunkcja działa równie dobrze dla obu list liczb całkowitych i list ciągów znaków.
Greg Hewgill,
118

Nie wymyślaj ponownie koła, jeśli używasz Django :

>>> from django.contrib.admin.utils import flatten
>>> l = [[1,2,3], [4,5], [6]]
>>> flatten(l)
>>> [1, 2, 3, 4, 5, 6]

... Pandy :

>>> from pandas.core.common import flatten
>>> list(flatten(l))

... Itertools :

>>> import itertools
>>> flatten = itertools.chain.from_iterable
>>> list(flatten(l))

... Matplotlib

>>> from matplotlib.cbook import flatten
>>> list(flatten(l))

... Unipath :

>>> from unipath.path import flatten
>>> list(flatten(l))

... Narzędzia konfiguracji :

>>> from setuptools.namespaces import flatten
>>> list(flatten(l))
Max Malysh
źródło
4
flatten = itertools.chain.from_iterablepowinna być poprawna odpowiedź
gekony
3
świetna odpowiedź! działa również dla l = [[[1, 2, 3], [4, 5]], 5] w przypadku pand
Markus Dutschke,
1
Podoba mi się rozwiązanie Pandas. Jeśli masz coś takiego: list_of_menuitems = [1, 2, [3, [4, 5, [6]]]], spowoduje to na: [1, 2, 3, 4, 5, 6]. Tęsknię za poziomem spłaszczenia.
imjoseangel
115

Oto ogólne podejście, które stosuje się do liczb , ciągów , zagnieżdżonych list i mieszanych kontenerów.

Kod

#from typing import Iterable 
from collections import Iterable                            # < py38


def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

Uwagi :

Próbny

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(lst))                                         # nested lists
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

mixed = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"]              # numbers, strs, nested & mixed
list(flatten(mixed))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']

Odniesienie

  • To rozwiązanie zostało zmodyfikowane na podstawie przepisu w Beazley, D. i B. Jones. Przepis 4.14, Python Cookbook 3rd Ed., O'Reilly Media Inc. Sebastopol, Kalifornia: 2013.
  • Znaleziono wcześniejszy post SO , prawdopodobnie oryginalną demonstrację.
pylang
źródło
5
Właśnie napisałem prawie to samo, ponieważ nie widziałem twojego rozwiązania ... oto, czego szukałem „rekurencyjnie spłaszczam pełne listy wielokrotne” ... (+1)
Martin Thoma
3
@MartinThoma Bardzo mile widziane. Do Twojej wiadomości, jeśli spłaszczanie zagnieżdżonych iteratorów jest powszechną praktyką, istnieją pakiety innych firm, które dobrze sobie z tym radzą. Może to zaoszczędzić na odkryciu koła. Wspomniałem more_itertoolsm.in. o dyskusji w tym poście. Twoje zdrowie.
pylang
Może traversemoż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.
Wolf
Możesz sprawdzać if hasattr(x, '__iter__')zamiast importować / sprawdzać względem Iterable, co również wyklucza łańcuchy.
Ryan Allen
powyższy kod wydaje się nie działać, jeśli jedna z zagnieżdżonych list ma listę ciągów. [1, 2, [3, 4], [4], [], 9, 9.5, „ssssss”, [„str”, „sss”, „ss”], [3, 4, 5]] wyjście: - [1, 2, 3, 4, 4, 9, 9.5, „ssssss”, 3, 4, 5]
sunnyX
51

Jeśli chcesz spłaszczyć strukturę danych, w której nie wiesz, jak głęboko jest zagnieżdżona, możesz użyć 1iteration_utilities.deepflatten

>>> from iteration_utilities import deepflatten

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(deepflatten(l, depth=1))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> l = [[1, 2, 3], [4, [5, 6]], 7, [8, 9]]
>>> list(deepflatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Jest to generator, więc musisz rzucić wynik na listlub 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.flattenktóry sam jest cienkim opakowaniem itertools.chain.from_iterable:

>>> from iteration_utilities import flatten
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(flatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Wystarczy dodać kilka czasów (na podstawie odpowiedzi Nico Schlömera, która nie zawierała funkcji przedstawionej w tej odpowiedzi):

wprowadź opis zdjęcia tutaj

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 sumbędzie najszybszy, jednak przez długi iterables tylko itertools.chain.from_iterable, iteration_utilities.deepflattenczy zagnieżdżonego pojmowania mieć wystarczającą wydajność przy itertools.chain.from_iterableczym najszybciej (jak już zauważył Nico Schlömer).

from itertools import chain
from functools import reduce
from collections import Iterable  # or from collections.abc import Iterable
import operator
from iteration_utilities import deepflatten

def nested_list_comprehension(lsts):
    return [item for sublist in lsts for item in sublist]

def itertools_chain_from_iterable(lsts):
    return list(chain.from_iterable(lsts))

def pythons_sum(lsts):
    return sum(lsts, [])

def reduce_add(lsts):
    return reduce(lambda x, y: x + y, lsts)

def pylangs_flatten(lsts):
    return list(flatten(lsts))

def flatten(items):
    """Yield items from any nested iterable; see REF."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            yield from flatten(x)
        else:
            yield x

def reduce_concat(lsts):
    return reduce(operator.concat, lsts)

def iteration_utilities_deepflatten(lsts):
    return list(deepflatten(lsts, depth=1))


from simple_benchmark import benchmark

b = benchmark(
    [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add,
     pylangs_flatten, reduce_concat, iteration_utilities_deepflatten],
    arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)},
    argument_name='number of inner lists'
)

b.plot()

1 Oświadczenie: Jestem autorem tej biblioteki

MSeifert
źródło
sumnie działa już na dowolnych sekwencjach od samego początku 0, czyniąc functools.reduce(operator.add, sequences)zamianę (czy nie cieszymy się, że usunęli je reducez wbudowanych?) Gdy znane są typy, użycie może być szybsze type.__add__.
Yann Vernier
@YannVernier Dzięki za informację. Myślałem, że uruchomiłem te testy porównawcze w Pythonie 3.6 i zadziałało sum. Czy wiesz, w których wersjach języka Python przestał działać?
MSeifert
Byłem trochę w błędzie. 0jest 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. Implementuje foldlzamiast foldl1. Ten sam problem pojawia się w 2.7.
Yann Vernier
39

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.

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10000'
    ).timeit(100)
2.0440959930419922

Wersja sumy nadal działa przez ponad minutę i nie została jeszcze przetworzona!

W przypadku średnich list:

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
20.126545906066895
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
22.242258071899414
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
16.449732065200806

Korzystanie z małych list i timeit: number = 1000000

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
2.4598159790039062
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.5289170742034912
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.0598428249359131
Nadia Alramli
źródło
23
dla naprawdę maleńkiej listy, np. takiej z 3 listami podrzędnymi, być może - ale ponieważ wydajność sumy idzie w parze z O (N ** 2), podczas gdy lista rozumienia idzie w górę z O (N), tylko zwiększenie listy wejściowej trochę odwróci rzeczy - - w rzeczywistości LC będzie „nieskończenie szybszy” niż suma na granicy, gdy N wzrośnie. Byłem odpowiedzialny za zaprojektowanie sumy i wykonanie jej pierwszej implementacji w środowisku wykonawczym Pythona i nadal żałuję, że nie znalazłem sposobu, aby skutecznie ograniczyć ją do sumowania liczb (w czym jest naprawdę dobra) i zablokować „atrakcyjne uciążliwości”, jakie oferuje ludziom którzy chcą „sumować” listy ;-).
Alex Martelli,
38

Wydaje się, że jest to zamieszanie operator.add! Gdy dodasz dwie listy razem, poprawnym terminem jest concatnie dodawanie. operator.concatjest to, czego potrzebujesz.

Jeśli myślisz o funkcjonalności, jest to tak proste:

>>> from functools import reduce
>>> list2d = ((1, 2, 3), (4, 5, 6), (7,), (8, 9))
>>> reduce(operator.concat, list2d)
(1, 2, 3, 4, 5, 6, 7, 8, 9)

Widzisz, że redukcja respektuje typ sekwencji, więc kiedy dostarczysz krotkę, otrzymasz krotkę. Spróbujmy z listą:

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> reduce(operator.concat, list2d)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Aha, odzyskałeś listę.

Jak o wydajności ::

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> %timeit list(itertools.chain.from_iterable(list2d))
1000000 loops, best of 3: 1.36 µs per loop

from_iterablejest dość szybki! Ale nie można tego porównywać concat.

>>> list2d = ((1, 2, 3),(4, 5, 6), (7,), (8, 9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop
Meitham
źródło
1
Hmm, żeby być uczciwym drugim przykładem powinna być także lista (lub pierwsza krotka?)
Mr_and_Mrs_D
2
Korzystanie z tak małych danych wejściowych nie jest dobrym porównaniem. Dla 1000 sekwencji o długości 1000 dostaję 0,037 sekundy dla list(chain.from_iterable(...))i 2,5 sekundy dla reduce(concat, ...). Problem polega na tym, że reduce(concat, ...)ma kwadratowy czas działania, podczas gdy chainjest liniowy.
kaya3
33

Dlaczego korzystasz z rozszerzenia?

reduce(lambda x, y: x+y, l)

To powinno działać dobrze.

Andrea Ambu
źródło
7
dla python3from functools import reduce
andorov
Przepraszam, że to bardzo wolno, zobacz pozostałe odpowiedzi
Mr_and_Mrs_D
Jest to zdecydowanie najłatwiejsze do zrozumienia, a zarazem krótkie rozwiązanie, które działa w Pythonie 2 i 3. Zdaję sobie sprawę, że wielu ludzi w Pythonie przetwarza dane, w których jest ogromna ilość danych do przetworzenia, a tym samym bardzo zależy na szybkości, ale kiedy piszą skrypt powłoki i mają tylko kilkadziesiąt elementów na kilku podlistach, to jest idealne.
Asfand Qazi
27

Rozważ zainstalowanie more_itertoolspakietu.

> pip install more_itertools

Jest dostarczany z implementacją flatten( źródło , z przepisów itertools ):

import more_itertools


lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.flatten(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

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

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.collapse(lst)) 
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

lst = [[1, 2, 3], [[4, 5, 6]], [[[7]]], 8, 9]              # complex nesting
list(more_itertools.collapse(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
pylang
źródło
W rzeczy samej. To powinna być zaakceptowana odpowiedź
brunetton,
Jeśli możesz sobie pozwolić na dodanie pakietu do swojego projektu - ta odpowiedź jest najlepsza
viddik13
22

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:

reduce(lambda x,y: x.extend(y) or x, l)

Uwaga: rozszerzenie jest bardziej wydajne niż + na listach.

Igor Krivokon
źródło
7
extendjest lepiej wykorzystywane jako newlist = [], extend = newlist.extend, for sublist in l: extend(l)gdyż unika (dość duże) narzut lambda, odnośnika atrybut x, a or.
agf
do pytona 3 dodajfrom functools import reduce
Markus Dutschke
17
def flatten(l, a):
    for i in l:
        if isinstance(i, list):
            flatten(i, a)
        else:
            a.append(i)
    return a

print(flatten([[[1, [1,1, [3, [4,5,]]]], 2, 3], [4, 5],6], []))

# [1, 1, 1, 3, 4, 5, 2, 3, 4, 5, 6]
Indygowiec
źródło
def flatten(l, a=None): if a is None: a = [][...]
Poik
16

Wersja rekurencyjna

x = [1,2,[3,4],[5,[6,[7]]],8,9,[10]]

def flatten_list(k):
    result = list()
    for i in k:
        if isinstance(i,list):

            #The isinstance() function checks if the object (first argument) is an 
            #instance or subclass of classinfo class (second argument)

            result.extend(flatten_list(i)) #Recursive call
        else:
            result.append(i)
    return result

flatten_list(x)
#result = [1,2,3,4,5,6,7,8,9,10]
Saurabh Singh
źródło
1
fajnie, nie wymaga importu i jest jasne, co robi ... spłaszczanie listy, kropka :)
Goran B.,
1
po prostu genialne!
Sachin Sharma
15

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.

import matplotlib
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
print(list(matplotlib.cbook.flatten(l)))
l2 = [[1, 2, 3], [4, 5, 6], [7], [8, [9, 10, [11, 12, [13]]]]]
print list(matplotlib.cbook.flatten(l2))

Wynik:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

Jest to 18x szybciej niż podkreślenie ._. Spłaszczyć:

Average time over 1000 trials of matplotlib.cbook.flatten: 2.55e-05 sec
Average time over 1000 trials of underscore._.flatten: 4.63e-04 sec
(time for underscore._)/(time for matplotlib.cbook) = 18.1233394636
EL_DON
źródło
14

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.

l = ['aaa', 'bb', 'cccccc', ['xx', 'yyyyyyy']]

Zaakceptowana odpowiedź, która nie zadziałała:

flat_list = [item for sublist in l for item in sublist]
print(flat_list)
['a', 'a', 'a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'c', 'xx', 'yyyyyyy']

Nowe proponowane rozwiązania, które zrobił dla mnie pracować:

flat_list = []
_ = [flat_list.extend(item) if isinstance(item, list) else flat_list.append(item) for item in l if item]
print(flat_list)
['aaa', 'bb', 'cccccc', 'xx', 'yyyyyyy']
użytkownik9074332
źródło
13

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:

def list_flatten(l, a=None):
    #check a
    if a is None:
        #initialize with empty list
        a = []

    for i in l:
        if isinstance(i, list):
            list_flatten(i, a)
        else:
            a.append(i)
    return a

Testowanie:

In [2]: lst = [1, 2, [3], [[4]],[5,[6]]]

In [3]: lst
Out[3]: [1, 2, [3], [[4]], [5, [6]]]

In [11]: list_flatten(lst)
Out[11]: [1, 2, 3, 4, 5, 6]
Deleet
źródło
13

Najprostsze wydają mi się:

>>> import numpy as np
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> print (np.concatenate(l))
[1 2 3 4 5 6 7 8 9]
diabeł w szczegółach
źródło
Nie działa w przypadku list o różnych wymiarach. -1
nurub
10

Można również skorzystać z mieszkania NumPy :

import numpy as np
list(np.array(l).flat)

Edytuj 02.02.2016: Działa tylko wtedy, gdy listy podrzędne mają identyczne wymiary.

mdh
źródło
czy byłoby to optymalne rozwiązanie?
RetroCode
6

Możesz użyć numpy:
flat_list = list(np.concatenate(list_of_list))

A. Attia
źródło
Działa to również w przypadku list numerycznych, łańcuchów i list mieszanych
Nitin,
2
Nie działa w przypadku nierównomiernie zagnieżdżonych danych, takich jak[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

Jeśli chcesz zrezygnować z niewielkiej prędkości, aby uzyskać czystszy wygląd, możesz użyć numpy.concatenate().tolist()lub numpy.concatenate().ravel().tolist():

import numpy

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] * 99

%timeit numpy.concatenate(l).ravel().tolist()
1000 loops, best of 3: 313 µs per loop

%timeit numpy.concatenate(l).tolist()
1000 loops, best of 3: 312 µs per loop

%timeit [item for sublist in l for item in sublist]
1000 loops, best of 3: 31.5 µs per loop

Możesz dowiedzieć się więcej tutaj w dokumentach numpy.concatenate i numpy.ravel

mkultra
źródło
1
Nie działa w przypadku nierównomiernie zagnieżdżonych list, takich jak[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

Najszybsze rozwiązanie, jakie znalazłem (dla dużej listy i tak):

import numpy as np
#turn list into an array and flatten()
np.array(l).flatten()

Gotowy! Możesz oczywiście zmienić go z powrotem w listę, wykonując listę (l)

Kanadyjczyk
źródło
1
To źle, spłaszczenie zmniejszy wymiary nd tablicy do jednego, ale nie połączy list wewnątrz jako jednego.
Ando Jurai
5

Prosty kod dla underscore.pywentylatora pakietu

from underscore import _
_.flatten([[1, 2, 3], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Rozwiązuje wszystkie problemy spłaszczania (brak elementu listy lub złożone zagnieżdżenie)

from underscore import _
# 1 is none list item
# [2, [3]] is complex nesting
_.flatten([1, [2, [3]], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Możesz zainstalować za underscore.pypomocą pip

pip install underscore.py
Vu Anh
źródło
Podobnie możesz użyć pydash . Uważam, że ta wersja jest znacznie bardziej czytelna niż zrozumienie listy lub inne odpowiedzi.
gliemezis
2
To jest bardzo wolne.
Nico Schlömer,
2
Dlaczego ma moduł o nazwie _? To wygląda na złe imię. Zobacz stackoverflow.com/a/5893946/6605826
EL_DON
2
@EL_DON: Ze strony readme underscore.py „Underscore.py to port Pythona doskonałej biblioteki javascript underscore.js”. Myślę, że to jest powód tego imienia. I tak, to nie jest dobre imię dla pytona
Vu Anh
5
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])
englealuze
źródło
Błąd dla python2.7 dla przykładowej listy zagnieżdżonej w pytaniu:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON
@EL_DON przetestowany na Pythonie 2.7.5. działa dobrze
englealuze
5

Uwaga : poniżej dotyczy Python 3.3+, ponieważ używa yield_from. sixjest 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 i itertools.chain.from_iterable.

Rozważ jednak ten nieco bardziej złożony przypadek:

>>> obj = [[1, 2, 3], [4, 5], 6, 'abc', [7], [8, [9, 10]]]

Tutaj jest kilka problemów:

  • Jeden element, 6to tylko skalar; nie jest iterowalny, więc powyższe trasy zawiodą tutaj.
  • Jeden element 'abc', jest technicznie iterable (wszystkie strs są). Jednak czytając trochę między wierszami, nie chcesz traktować go jako takiego - chcesz traktować go jako pojedynczy element.
  • Ostatni element [8, [9, 10]]sam w sobie jest iterowalnym zagnieżdżonym. Podstawowe rozumienie listy i chain.from_iterablewyodrębnianie tylko „1 poziom w dół”.

Możesz temu zaradzić w następujący sposób:

>>> from collections import Iterable
>>> from six import string_types

>>> def flatten(obj):
...     for i in obj:
...         if isinstance(i, Iterable) and not isinstance(i, string_types):
...             yield from flatten(i)
...         else:
...             yield i


>>> list(flatten(obj))
[1, 2, 3, 4, 5, 6, 'abc', 7, 8, 9, 10]

Tutaj sprawdzasz, czy podelement (1) jest iterowalny Iterable, z ABC itertools, ale też chcesz upewnić się, że (2) element nie jest „ciągiem”.

Brad Solomon
źródło
1
Jeśli nadal jesteś zainteresowany kompatybilnością z Python 2, zmień yield fromna forpętlę, np.for x in flatten(i): yield x
pylang
5
flat_list = []
for i in list_of_list:
    flat_list+=i

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.

Deepak Yadav
źródło
5
from nltk import flatten

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
flatten(l)

Zaletą tego rozwiązania w porównaniu z większością innych tutaj jest to, że jeśli masz listę jak:

l = [1, [2, 3], [4, 5, 6], [7], [8, 9]]

podczas gdy większość innych rozwiązań generuje błąd, to rozwiązanie je obsługuje.

Alijy
źródło
Pytanie zawiera „listę list”, ale twoja przykładowa lista zawiera element inny niż lista. Większość innych rozwiązań pozostaje przy pierwotnym pytaniu. Twoje rozwiązanie rozwiązuje szerszy problem, ale wymaga także nie-podstawowego pakietu Python (nltk), który musi zostać zainstalowany najpierw.
simonobo
4

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

def make_list_flat (l):
    flist = []
    flist.extend ([l]) if (type (l) is not list) else [flist.extend (make_list_flat (e)) for e in l]
    return flist

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = make_list_flat(a)
print (flist)

Dane wyjściowe to

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Działa to w pierwszej kolejności. Rekurencja spada w dół, aż znajdzie element inny niż lista, a następnie rozszerza zmienną lokalną, flista następnie przywraca ją do elementu nadrzędnego. Ilekroć flistjest zwracany, jest on rozszerzony na listę rodziców flistw 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.

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = []
def make_list_flat (l):
    flist.extend ([l]) if (type (l) is not list) else [make_list_flat (e) for e in l]

make_list_flat(a)
print (flist)

Wyjście jest znowu

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Chociaż w tej chwili nie jestem pewien co do wydajności.

phoxis
źródło
Po co rozszerzać ([l]) zamiast dołączać (l)?
Maciek
3

Kolejne niezwykłe podejście, które działa dla hetero- i jednorodnych list liczb całkowitych:

from typing import List


def flatten(l: list) -> List[int]:
    """Flatten an arbitrary deep nested list of lists of integers.

    Examples:
        >>> flatten([1, 2, [1, [10]]])
        [1, 2, 1, 10]

    Args:
        l: Union[l, Union[int, List[int]]

    Returns:
        Flatted list of integer
    """
    return [int(i.strip('[ ]')) for i in str(l).split(',')]
tharndt
źródło
To tylko bardziej skomplikowany i nieco wolniejszy sposób od tego, co ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 już wcześniej opublikowało. Wczoraj na nowo opracowałem jego propozycję, więc obecnie takie podejście wydaje się dość popularne;)
Darkonaut,
Niezupełnie: 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]
tharndt
moim kodem jako jednej flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
linijki
1
Rzeczywiście masz +1, propozycja 0003000 nie będzie działać z liczbami wielocyfrowymi, ja też tego wcześniej nie testowałem, chociaż powinno to być oczywiste. Możesz uprościć swój kod i pisać [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.
Darkonaut
2
Niestety nie. Ale widziałem ten kod niedawno tutaj: Python Practice Book 6.1.2
tharndt