Frage Eine flache Liste aus der Liste der Listen in Python erstellen


Ich frage mich, ob es eine Abkürzung gibt, um eine einfache Liste aus einer Liste von Listen in Python zu erstellen.

Ich kann das in einer For-Schleife machen, aber vielleicht gibt es einen coolen "One-Liner"? Ich habe es mit versucht reduzieren, aber ich bekomme einen Fehler.

Code

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

Fehlermeldung

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'

2069
2018-06-04 20:30


Ursprung


Antworten:


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

was bedeutet:

for sublist in l:
    for item in sublist:
        flat_list.append(item)

ist schneller als die bisherigen Shortcuts. (l ist die Liste zu reduzieren.)

Hier ist eine entsprechende Funktion:

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

Wie immer können Sie den Beweis verwenden timeit Modul in der Standardbibliothek:

$ 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

Erläuterung: Die Verknüpfungen basieren auf + (einschließlich der impliziten Verwendung in sum) sind notwendigerweise O(L**2) wenn L-Unterlisten vorhanden sind - da die Zwischenergebnisliste immer länger wird, wird bei jedem Schritt ein neues Zwischenergebnislistenobjekt zugewiesen und alle Elemente des vorherigen Zwischenergebnisses müssen kopiert werden (sowie einige neue hinzugefügt werden) Am Ende). Also (für die Einfachheit und ohne tatsächlichen Verlust der Allgemeinheit) sagen Sie, dass Sie L Unterlisten von I Stücken haben: die ersten I Gegenstände werden L-1 mal hin und her kopiert, die zweiten I Gegenstände L-2 mal, und so weiter; Gesamtanzahl der Kopien ist I mal die Summe von x für x von 1 bis L ausgeschlossen, d.h. I * (L**2)/2.

Das Listenverständnis generiert nur einmal eine Liste und kopiert jedes Objekt (genau einmal von seinem ursprünglichen Wohnort zur Ergebnisliste).


2984
2018-06-04 20:37



Sie können verwenden itertools.chain():

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

oder, bei Python> = 2.6, verwenden itertools.chain.from_iterable() was das Auspacken der Liste nicht erfordert:

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

Dieser Ansatz ist wohl besser lesbar als [item for sublist in l for item in sublist] und scheint auch schneller zu sein:

[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
10000 loops, best of 3: 24.2 usec per loop
[me@home]$ 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: 45.2 usec per loop
[me@home]$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 488 usec per loop
[me@home]$ 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: 522 usec per loop
[me@home]$ python --version
Python 2.7.3

1079
2018-06-04 21:06



Anmerkung des Autors: Das ist ineffizient. Aber Spaß, weil Monaden toll sind. Es ist nicht für Python-Produktionscode geeignet.

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

Dies summiert nur die Elemente von iterable, die im ersten Argument übergeben wurden, und behandelt das zweite Argument als Anfangswert der Summe (falls nicht angegeben, 0 wird stattdessen verwendet und dieser Fall gibt Ihnen einen Fehler).

Weil Sie verschachtelte Listen summieren, erhalten Sie tatsächlich [1,3]+[2,4] Als ein Resultat aus sum([[1,3],[2,4]],[]), was gleich ist [1,3,2,4].

Beachten Sie, dass nur Listenlisten funktionieren. Für Listen von Listen benötigen Sie eine andere Lösung.


636
2018-06-04 20:35



Ich habe die meisten vorgeschlagenen Lösungen mit getestet perfot (ein Haustier-Projekt von mir, im Wesentlichen ein Wrapper herum timeit), und gefunden

list(itertools.chain.from_iterable(a))

um die schnellste Lösung zu sein (wenn mehr als 10 Listen verkettet sind).

enter image description here


Code zum Reproduzieren der Handlung:

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 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,
    kernels=[
        forfor, sum_brackets, functools_reduce, itertools_chain, numpy_flat,
        numpy_concatenate
        ],
    n_range=[2**k for k in range(16)],
    logx=True,
    logy=True,
    xlabel='num lists'
    )

129
2017-07-26 09:38



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]

Das extend() Methode in Ihrem Beispiel ändert sich x anstatt einen nützlichen Wert zurückzugeben (welcher reduce() erwartet).

Ein schneller Weg, um das zu tun reduce Version wäre

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

99
2018-06-04 20:35



Hier ist ein allgemeiner Ansatz, der gilt für Zahlen, Saiten, verschachtelt Listen und gemischt Behälter.

Code

from collections import Iterable


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

Hinweis: In Python 3, yield from flatten(x) ersetzen können for sub_x in flatten(x): yield sub_x

Demo

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

Referenz

  • Diese Lösung wird von einem Rezept in geändert Beazley, D. und B. Jones. Rezept 4.14, Python Kochbuch 3. Ausgabe, O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Früher gefunden SO Postmöglicherweise die ursprüngliche Demonstration.

54
2017-11-29 04:14



Ich nehme meine Aussage zurück. Summe ist nicht der Gewinner. Obwohl es schneller ist, wenn die Liste klein ist. Bei größeren Listen verschlechtert sich die Performance jedoch erheblich. 

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

Die Summenversion läuft noch länger als eine Minute und hat noch nicht verarbeitet!

Für mittlere Listen:

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

Verwenden Sie kleine Listen und Zeit: Nummer = 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

31
2018-06-04 20:46



Warum benutzt du ext?

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

Dies sollte gut funktionieren.


25
2018-06-04 20:38



Es scheint eine Verwirrung mit zu geben operator.add! Wenn Sie zwei Listen zusammenfügen, ist der korrekte Ausdruck dafür concatnicht hinzufügen. operator.concat ist was du brauchst.

Wenn du funktionstüchtig denkst, ist es so einfach:

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

Sie sehen, dass reduce den Sequenztyp respektiert. Wenn Sie also ein Tupel angeben, erhalten Sie ein Tupel zurück. Lass uns mit einer Liste versuchen ::

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

Aha, du bekommst eine Liste zurück.

Wie wäre es mit Leistung ::

>>> 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_iterable ist ziemlich schnell! Aber es ist kein Vergleich mit concat zu reduzieren.

>>> list2d = ((1,2,3),(4,5,6), (7,), (8,9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop

19
2017-09-14 15:09



Wenn Sie eine Datenstruktur reduzieren möchten, in der Sie nicht wissen, wie tief sie verschachtelt ist, können Sie sie verwenden iteration_utilities.deepflatten1

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

Es ist ein Generator, also müssen Sie das Ergebnis in ein list oder explizit darüber iterieren.


Um nur eine Ebene zu reduzieren und wenn jedes der Elemente selbst iterierbar ist, können Sie auch verwenden iteration_utilities.flatten welches selbst nur ein dünner Wrapper ist 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]

Nur um einige Zeitpunkte hinzuzufügen (basierend auf Nico Schlömers Antwort, die die in dieser Antwort präsentierte Funktion nicht enthielt):

enter image description here

Es ist ein Log-Log-Plot, der die große Spannweite der Werte berücksichtigt. Für qualitatives Denken: Lower ist besser.

Die Ergebnisse zeigen, dass dann, wenn das Iterable nur ein paar innere Iterables enthält sum wird am schnellsten, aber für lange iterables nur die itertools.chain.from_iterable, iteration_utilities.deepflatten oder das verschachtelte Verständnis hat eine angemessene Leistung mit itertools.chain.from_iterable der Schnellste sein (wie schon Nico Schlömer bemerkt).

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 Disclaimer: Ich bin der Autor dieser Bibliothek


17
2017-11-26 00:20