python - éléments - Comment faire une liste à plat d'une liste de listes?




taille d'une liste python (20)

Je me demande s'il existe un raccourci pour créer une liste simple à partir d'une liste de listes en Python.

Je peux le faire en boucle, mais peut-être y a-t-il un "one-liner"? Je l'ai essayé avec réduire , mais j'ai une erreur.

Code

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

Message d'erreur

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

Code simple pour les fans du package underscore.py

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

Il résout tous les problèmes d'aplatissement (aucun élément de liste ou imbrication complexe)

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]

Vous pouvez installer underscore.py avec pip

pip install underscore.py

Il semble y avoir une confusion avec operator.add ! Lorsque vous ajoutez deux listes ensemble, le terme correct pour cela est concat , pas add. operator.concat est ce que vous devez utiliser.

Si vous pensez fonctionnel, c'est aussi simple que cela:

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

Vous voyez réduire le type de séquence, alors lorsque vous fournissez un tuple, vous récupérez un tuple. Essayons avec une liste ::

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

Aha, vous obtenez une liste.

Qu'en est-il de la performance ::

>>> 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 est assez rapide! Mais ce n'est pas une comparaison pour réduire avec 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

La réponse acceptée ne fonctionnait pas pour moi s’agissant de listes textuelles de longueurs variables. Voici une autre approche qui a fonctionné pour moi.

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

Réponse acceptée qui n'a pas fonctionné:

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

Nouvelle solution proposée qui a fonctionné pour moi:

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

La raison pour laquelle votre fonction n'a pas fonctionné: la commande extend étend le tableau sur place et ne le renvoie pas. Vous pouvez toujours retourner x de lambda, en utilisant une astuce:

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

Remarque: extend est plus efficace que + sur les listes.


Pensez à installer le paquet more_itertools .

> pip install more_itertools

Il est livré avec une implémentation pour flatten ( source , à partir des recettes d'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]

A partir de la version 2.4, vous pouvez aplatir des more_itertools.collapse imbriqués plus complexes et plus complexes avec more_itertools.collapse ( source , contribué par 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]

Pourquoi utilisez-vous extend?

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

Cela devrait bien fonctionner.


Si vous voulez aplatir une structure de données où vous ne savez pas à quelle profondeur elle est imbriquée, vous pouvez utiliser iteration_utilities.deepflatten 1

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

C'est un générateur, vous devez donc convertir le résultat en list ou l'itérer explicitement.

Pour n'aplatir qu'un seul niveau et si chacun des éléments est lui-même itérable, vous pouvez également utiliser iteration_utilities.flatten qui n'est en soi qu'une mince couche autour de 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]

Juste pour ajouter quelques timings (basé sur la réponse de Nico Schlömer qui n'incluait pas la fonction présentée dans cette réponse):

Il s’agit d’un graphique log-log qui tient compte de la vaste gamme de valeurs couvertes. Pour un raisonnement qualitatif: Plus c'est bas, mieux c'est.

Les résultats montrent que si l'itérable ne contient que quelques iterables internes, alors sum sera le plus rapide. Cependant, pour de longues itertools.chain.from_iterable , seul itertools.chain.from_iterable , iteration_utilities.deepflatten ou la compréhension imbriquée ont des performances raisonnables, itertools.chain.from_iterable étant le plus rapide. (comme déjà remarqué par 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 Disclaimer: je suis l'auteur de cette bibliothèque


Suivre me semble le plus simple:

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

Une fonction défectueuse de la fonction d'Anil ci-dessus est qu'elle oblige l'utilisateur à toujours spécifier manuellement le deuxième argument comme étant une liste vide [] . Cela devrait plutôt être un défaut. En raison du fonctionnement des objets Python, ceux-ci doivent être définis dans la fonction et non dans les arguments.

Voici une fonction de travail:

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

Essai:

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]

Voici une approche générale qui s’applique aux nombres , aux chaînes , aux listes imbriquées et aux conteneurs mixtes .

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

Remarque: en Python 3, le yield from flatten(x) peut remplacer for sub_x in flatten(x): yield sub_x

Démo

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

Référence

  • Cette solution est modifiée à partir d’une recette de Beazley, D. et B. Jones. Recette 4.14, livre de recettes Python, 3e éd., O'Reilly Media Inc., Sebastopol, CA: 2013.
  • Trouvé un précédent article SO , peut-être la démonstration originale.

Vous pouvez utiliser numpy:
flat_list = list(np.concatenate(list_of_list))


matplotlib.cbook.flatten() fonctionnera pour les listes imbriquées même si elles imbriquent plus profondément que l'exemple.

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

Résultat:

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

Ceci est 18 fois plus rapide que le trait de soulignement ._. Flatten:

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

Note de l'auteur : C'est inefficace. Mais amusant, car les monades sont géniales. Ce n'est pas approprié pour le code Python de production.

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

Ceci additionne simplement les éléments de iterable passés dans le premier argument, le second argument étant considéré comme la valeur initiale de la somme (sinon, 0 est utilisé à la place et ce cas vous donnera une erreur).

Comme vous additionnez des listes imbriquées, vous obtenez réellement [1,3]+[2,4] à la suite de la sum([[1,3],[2,4]],[]) , ce qui correspond à [1,3,2,4] .

Notez que ne fonctionne que sur des listes de listes. Pour les listes de listes de listes, vous aurez besoin d'une autre solution.


Remarque : ci-dessous s’applique à Python 3.3+ car il utilise yield_from. sixest également un package tiers, bien qu'il soit stable. Alternativement, vous pouvez utiliser sys.version.

Dans le cas de obj = [[1, 2,], [3, 4], [5, 6]], toutes les solutions ici sont bonnes, y compris la compréhension de liste et itertools.chain.from_iterable.

Cependant, considérons ce cas légèrement plus complexe:

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

Il y a plusieurs problèmes ici:

  • Un élément, 6n'est qu'un scalaire; ce n'est pas itérable, donc les routes ci-dessus vont échouer ici.
  • Un élément, 'abc', est techniquement itérables (tous strs sont). Cependant, en lisant un peu entre les lignes, vous ne voulez pas le traiter comme tel, mais plutôt comme un élément unique.
  • L'élément final, [8, [9, 10]]est lui-même un itératif imbriqué. Compréhension de base de la liste et chain.from_iterableextrait uniquement "1 level down".

Vous pouvez y remédier comme suit:

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

Ici, vous vérifiez que le sous-élément (1) est itérable Iterable, un ABC de itertools, mais vous voulez également vous assurer que (2) l'élément n'est pas "chaîne".


La solution la plus rapide que j'ai trouvée (pour les grandes listes quand même):

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

Terminé! Vous pouvez bien sûr le transformer en liste en exécutant list (l)


Vous pouvez éviter les appels récursifs à la pile en utilisant assez simplement une structure de données de pile réelle.

alist = [1,[1,2],[1,2,[4,5,6],3, "33"]]
newlist = []

while len(alist) > 0 :
  templist = alist.pop()
  if type(templist) == type(list()) :
    while len(templist) > 0 :
      temp = templist.pop()
      if type(temp) == type(list()) :
        for x in temp :
          templist.append(x)
      else :
        newlist.append(temp)
  else :
    newlist.append(templist)
print(list(reversed(newlist)))

Une méthode récursive simple utilisant reducefrom functoolset l' addopérateur sur les listes:

>>> from functools import reduce
>>> from operator import add
>>> flatten = lambda lst: [lst] if type(lst) is int else reduce(add, [flatten(ele) for ele in lst])
>>> flatten(l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

La fonction flattenprend en lstparamètre. Il boucles tous les éléments lstjusqu'à atteindre des entiers (peuvent également changer intà float, stretc. pour d' autres types de données), qui sont ajoutés à la valeur de retour de la récursion extérieure.

La récursivité, contrairement aux méthodes telles que les forboucles et les monades, est qu’il s’agit d’ une solution générale qui n’est pas limitée par la profondeur de la liste . Par exemple, une liste avec une profondeur de 5 peut être aplatie de la même manière que l:

>>> l2 = [[3, [1, 2], [[[6], 5], 4, 0], 7, [[8]], [9, 10]]]
>>> flatten(l2)
[3, 1, 2, 6, 5, 4, 0, 7, 8, 9, 10]

def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])

flat_list = []
for i in list_of_list:
    flat_list+=i

Ce code fonctionne également correctement car il ne fait que rallonger la liste. Bien que ce soit très similaire, mais n’en ayez qu’une pour la boucle. Donc, il a moins de complexité que d'ajouter 2 pour les boucles.


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

ce qui signifie:

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

est plus rapide que les raccourcis affichés jusqu'à présent. ( l est la liste à aplatir.)

Voici la fonction correspondante:

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

Pour preuve, comme toujours, vous pouvez utiliser le module timeit dans la bibliothèque standard:

$ 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

Explication: les raccourcis basés sur + (y compris l'utilisation implicite de sum ) sont nécessairement O(L**2) lorsqu'il y a L sous-listes - car la liste des résultats intermédiaires s'allonge, à chaque étape un nouveau résultat intermédiaire list object est alloué et tous les éléments du résultat intermédiaire précédent doivent être copiés (ainsi que quelques nouveaux ajoutés à la fin). Donc (pour la simplicité et sans perte de généralité) disons que vous avez L sous-listes de I articles chacun: les I premiers articles sont copiés dans les deux sens L-1 fois, les deuxièmes I articles L-2 fois, et ainsi de suite; Le nombre total de copies correspond à I fois la somme de x pour x de 1 à L exclu, c'est-à-dire I * (L**2)/2 .

La compréhension de liste ne génère qu'une liste, une fois, et copie chaque élément (de son lieu de résidence d'origine à la liste des résultats) également une fois.





flatten