Comment faire une liste plate à partir d'une liste de listes?

3379

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

Je peux le faire en forboucle, mais peut-être qu'il y a du "one-liner" cool? Je l'ai essayé avec reduce(), mais j'obtiens 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'
Emma
la source
20
Il y a une discussion approfondie à ce sujet ici: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , discutant de plusieurs méthodes d'aplatissement de listes de listes imbriquées arbitrairement. Une lecture intéressante!
RichieHindle
6
Certaines autres réponses sont meilleures, mais la raison pour laquelle la vôtre échoue est que la méthode «extend» renvoie toujours None. Pour une liste de longueur 2, cela fonctionnera mais retournera None. Pour une liste plus longue, il consommera les 2 premiers arguments, ce qui renvoie None. Il continue ensuite avec None.extend (<troisième argument>), ce qui provoque cette erro
mehtunguh
La solution @ shawn-chin est la plus pythonique ici, mais si vous avez besoin de conserver le type de séquence, disons que vous avez un tuple de tuples plutôt qu'une liste de listes, alors vous devez utiliser réduire (operator.concat, tuple_of_tuples). L'utilisation de operator.concat avec des tuples semble fonctionner plus rapidement que chain.from_iterables avec list.
Meitham

Réponses:

4800

Étant donné une liste de listes l,

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

ce qui signifie:

flat_list = []
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, vous pouvez utiliser le timeitmodule 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 dans sum) sont, par nécessité, O(L**2)lorsqu'il y a des sous-listes L - car la liste de résultats intermédiaire s'allonge, à chaque étape un nouvel objet de liste de résultats intermédiaire est alloué, et tous les éléments dans le résultat intermédiaire précédent doit être copié (ainsi que quelques nouveaux ajoutés à la fin). Donc, pour plus de simplicité et sans perte réelle de généralité, disons que vous avez chacun L sous-listes de I éléments: les premiers I éléments sont copiés d'avant en arrière L-1 fois, les seconds I éléments L-2 fois, et ainsi de suite; le nombre total de copies est I fois la somme de x pour x de 1 à L exclu, c'est-à-direI * (L**2)/2 .

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

Alex Martelli
la source
486
J'ai essayé un test avec les mêmes données, en utilisant 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))'. Il fonctionne un peu plus de deux fois plus vite que la compréhension de liste imbriquée qui est la plus rapide des alternatives présentées ici.
intuition
274
J'ai trouvé la syntaxe difficile à comprendre jusqu'à ce que je réalise que vous pouvez y penser exactement comme imbriqué pour les boucles. pour sous-liste en l: pour article en sous-liste: article de rendement
Rob Crowell
23
@BorisChervenkov: Notez que j'ai encapsulé l'appel list()pour réaliser l'itérateur dans une liste.
intuition
163
[feuille pour arbre en forêt pour feuille dans arbre] pourrait être plus facile à comprendre et à appliquer.
John Mee
80
@Joel, en fait de nos jours list(itertools.chain.from_iterable(l))est le meilleur - comme remarqué dans d'autres commentaires et la réponse de Shawn.
Alex Martelli
1570

Vous pouvez utiliser itertools.chain():

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

Ou vous pouvez utiliser itertools.chain.from_iterable()ce qui ne nécessite pas de déballer la liste avec l' *opérateur :

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain.from_iterable(list2d))
Shawn Chin
la source
13
C'est *la chose délicate qui rend chainmoins simple que la compréhension de la liste. Vous devez savoir que la chaîne réunit uniquement les itérables passés en tant que paramètres, et le * entraîne l'extension de la liste de niveau supérieur en paramètres, donc chainréunit tous ces itérables, mais ne descend pas plus loin. Je pense que cela rend la compréhension plus lisible que l'utilisation de la chaîne dans ce cas.
Tim Dierks
52
@TimDierks: Je ne suis pas sûr que "cela vous oblige à comprendre la syntaxe Python" est un argument contre l'utilisation d'une technique donnée en Python. Bien sûr, une utilisation complexe pourrait prêter à confusion, mais l'opérateur "splat" est généralement utile dans de nombreuses circonstances, et cela ne l'utilise pas de manière particulièrement obscure; rejeter toutes les fonctionnalités linguistiques qui ne sont pas nécessairement évidentes pour les utilisateurs débutants signifie que vous attachez une main derrière votre dos. Peut aussi jeter des listes de compréhension pendant que vous y êtes; les utilisateurs d'autres horizons trouveraient une forboucle qui est à plusieurs reprises appendplus évidente.
ShadowRanger
Cette réponse, et d'autres réponses ici, donnent un résultat incorrect si le niveau supérieur contient également une valeur. par exemple, list = [["abc","bcd"],["cde","def"],"efg"]entraînera une sortie de["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr
Il semble que l' *opérateur ne puisse pas être utilisé en python2
wkm il y a
908

Note de l'auteur : c'est inefficace. Mais amusant, car les monoïdes sont géniaux. Ce n'est pas approprié pour le code de production Python.

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

Cela résume simplement les éléments d'itérable passés dans le premier argument, traitant le second argument comme la valeur initiale de la somme (s'il n'est pas donné, 0est utilisé à la place et ce cas vous donnera une erreur).

Parce que vous additionnez des listes imbriquées, vous obtenez en fait [1,3]+[2,4]à la suite de sum([[1,3],[2,4]],[]), qui est égal à[1,3,2,4] .

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

Triptyque
la source
100
c'est assez soigné et intelligent mais je ne l'utiliserais pas car c'est déroutant à lire.
andrewrk
87
Il s'agit d'un algorithme de Shlemiel le peintre joelonsoftware.com/articles/fog0000000319.html - inutilement inefficace et inutilement laid.
Mike Graham
44
L'opération d'ajout sur les listes forme un Monoid, qui est l'une des abstractions les plus pratiques pour penser à une +opération dans un sens général (non limité aux seuls chiffres). Cette réponse mérite donc un +1 de ma part pour le traitement (correct) des listes en tant que monoïde. La performance est cependant préoccupante ...
ulidtko
7
@andrewrk Eh bien, certaines personnes pensent que c'est la façon la plus propre de le faire: youtube.com/watch?v=IOiZatlZtGU ceux qui ne comprennent pas pourquoi c'est cool ont juste besoin d'attendre quelques décennies jusqu'à ce que tout le monde le fasse de cette façon: ) utilisons des langages de programmation (et des abstractions) qui sont découverts et non inventés, Monoid est découvert.
jhegedus
11
c'est une manière très inefficace en raison de l'aspect quadratique de la somme.
Jean-François Fabre
462

J'ai testé la plupart des solutions suggérées avec perfplot (un de mes projets pour animaux de compagnie, essentiellement un emballage autour timeit), et j'ai trouvé

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

pour être la solution la plus rapide, à la fois lorsque de nombreuses petites listes et quelques longues listes sont concaténées. ( operator.iaddest tout aussi rapide.)

entrez la description de l'image ici

entrez la description de l'image ici


Code pour reproduire l'intrigue:

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
la source
25
Pour les énormes listes imbriquées, 'list (numpy.array (a) .flat)' est la plus rapide de toutes les fonctions ci-dessus.
Sara
Essayé avec regex: 'list (map (int, re.findall (r "[\ w] +", str (a))))'. La vitesse est un peu plus lente que numpy_concatenate
Justas
Existe-t-il un moyen de faire un perfplot 3D? nombre de tableaux par taille moyenne de tableau?
Leo
J'adore ta solution. Court, simple et efficace :-)
ShadyMBA
182
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]

La extend()méthode de votre exemple modifie xau lieu de renvoyer une valeur utile (qui reduce()attend).

Un moyen plus rapide de faire la reduceversion serait

>>> 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
la source
20
reduce(operator.add, l)serait la bonne façon de faire la reduceversion. Les fonctions intégrées sont plus rapides que les lambdas.
agf
3
@agf voici comment: * 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
Ceci est un algorithme de Shlemiel le peintre joelonsoftware.com/articles/fog0000000319.html
Mike Graham
2
cela ne peut être utilisé que pour integers. Mais que faire si la liste contient string?
Freddy
3
@Freddy: La operator.addfonction fonctionne aussi bien pour les listes d'entiers que pour les listes de chaînes.
Greg Hewgill
121

Ne réinventez pas la roue si vous utilisez Django :

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

... Pandas :

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

... Setuptools :

>>> from setuptools.namespaces import flatten
>>> list(flatten(l))
Max Malysh
la source
4
flatten = itertools.chain.from_iterabledevrait être la bonne réponse
geckos
3
très bonne réponse! fonctionne aussi pour l = [[[1, 2, 3], [4, 5]], 5] dans le cas des pandas
Markus Dutschke
1
J'aime la solution Pandas. Si vous avez quelque chose comme: list_of_menuitems = [1, 2, [3, [4, 5, [6]]]], il en résultera sur: [1, 2, 3, 4, 5, 6]. Ce qui me manque, c'est le niveau aplati.
imjoseangel
115

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

Code

#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

Remarques :

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, Python Cookbook 3rd Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Trouvé une publication SO antérieure , peut-être la démonstration originale.
pylang
la source
5
Je viens d'écrire à peu près la même chose, car je n'ai pas vu votre solution ... voici ce que j'ai cherché "aplatir récursivement plusieurs listes complètes" ... (+1)
Martin Thoma
3
@MartinThoma Très apprécié. Pour info, si l'aplatissement des itérables imbriqués est une pratique courante pour vous, il existe des packages tiers qui gèrent bien cela. Cela peut vous éviter de réinventer la roue. J'ai mentionné more_itertoolsentre autres discuté dans ce post. À votre santé.
pylang
Peut traverse- être pourrait également être un bon nom pour cette façon d'un arbre, alors que je le garderais moins universel pour cette réponse en s'en tenant aux listes imbriquées.
Wolf
Vous pouvez vérifier if hasattr(x, '__iter__')au lieu d'importer / vérifier Iterableet cela exclura également les chaînes.
Ryan Allen
le code ci-dessus ne semble pas fonctionner si l'une des listes imbriquées contient une liste de chaînes. [1, 2, [3, 4], [4], [], 9, 9.5, 'ssssss', ['str', 'sss', 'ss'], [3, 4, 5]] sortie: - [1, 2, 3, 4, 4, 9, 9.5, 'ssssss', 3, 4, 5]
sunnyX
51

Si vous voulez aplatir une structure de données où vous ne savez pas à quelle profondeur elle est imbriquée, vous pouvez utiliser 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]

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


Pour aplatir un seul niveau et si chacun des éléments est lui-même itérable, vous pouvez également utiliser iteration_utilities.flattence qui n'est lui-même qu'une mince enveloppe autour 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és sur la réponse de Nico Schlömer qui n'incluait pas la fonction présentée dans cette réponse):

entrez la description de l'image ici

Il s'agit d'un tracé de journal de bord pour s'adapter à la vaste gamme de valeurs réparties. Pour le raisonnement qualitatif: plus c'est bas, mieux c'est.

Les résultats montrent que si le itérables ne contient que quelques iterables internes alors sumsera le plus rapide, mais pour de longues iterables que les itertools.chain.from_iterable, iteration_utilities.deepflattenou la compréhension imbriquée ont des performances raisonnable 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 Avertissement: je suis l'auteur de cette bibliothèque

MSeifert
la source
sumne fonctionne plus sur les séquences arbitraires au début 0, ce qui rend functools.reduce(operator.add, sequences)le remplacement (ne sommes-nous pas heureux qu'ils aient été supprimés reducedes prédéfinis?). Lorsque les types sont connus, leur utilisation peut être plus rapide type.__add__.
Yann Vernier
@YannVernier Merci pour l'information. Je pensais avoir exécuté ces tests sur Python 3.6 et cela a fonctionné sum. Savez-vous par hasard sur quelles versions de Python il a cessé de fonctionner?
MSeifert
Je me trompais un peu. 0est juste la valeur de départ par défaut, donc ça marche si on utilise l' argument start pour commencer avec une liste vide ... mais ça casse toujours des chaînes spéciales et me dit d'utiliser join. Il implémente foldlau lieu de foldl1. Le même problème apparaît dans 2.7.
Yann Vernier
39

Je reprends ma déclaration. la somme n'est pas gagnante. Bien qu'il soit plus rapide lorsque la liste est petite. Mais les performances se dégradent considérablement avec de plus grandes listes.

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

La version sum fonctionne toujours depuis plus d'une minute et n'a pas encore été traitée!

Pour les listes moyennes:

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

Utilisation de petites listes et de timeit: nombre = 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
la source
23
pour une liste vraiment minuscule, par exemple une avec 3 sous-listes, peut-être - mais puisque les performances de sum vont avec O (N ** 2) tandis que la compréhension de la liste va avec O (N), le simple fait d'augmenter un peu la liste d'entrée inversera les choses - - en effet la LC sera "infiniment plus rapide" que la somme à la limite à mesure que N croît. J'étais responsable de la conception de sum et de sa première implémentation dans le runtime Python, et je souhaite toujours avoir trouvé un moyen de le limiter efficacement à la somme des nombres (ce qu'il est vraiment bon) et de bloquer la "nuisance attrayante" qu'il offre aux gens qui veulent "additionner" les listes ;-).
Alex Martelli
38

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

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

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

Vous voyez réduire respecte le type de séquence, donc 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 récupérez une liste.

Que diriez-vous 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_iterableest assez rapide! Mais ce n'est pas une comparaison avec laquelle réduire 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
la source
1
Hmm pour être juste, le deuxième exemple devrait être également répertorié (ou le premier tuple?)
Mr_and_Mrs_D
2
L'utilisation de si petites entrées n'est pas vraiment une comparaison équitable. Pour 1000 séquences de longueur 1000, j'obtiens 0,037 seconde pour list(chain.from_iterable(...))et 2,5 secondes pour reduce(concat, ...). Le problème est qu'il reduce(concat, ...)a un temps d'exécution quadratique, alors qu'il chainest linéaire.
kaya3
33

Pourquoi utilisez-vous extend?

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

Cela devrait bien fonctionner.

Andrea Ambu
la source
7
pour python3from functools import reduce
andorov
Désolé, c'est vraiment lent voir le reste des réponses
Mr_and_Mrs_D
C'est de loin la solution la plus simple à comprendre mais la plus courte qui fonctionne sur Python 2 et 3. Je me rends compte que beaucoup de gens Python sont dans le traitement de données où il y a d'énormes quantités de données à traiter et donc se soucient beaucoup de la vitesse, mais quand vous écrivent un script shell et n'ont que quelques dizaines d'éléments dans quelques sous-listes, alors c'est parfait.
Asfand Qazi
27

Pensez à installer le more_itertoolspackage.

> pip install more_itertools

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

Depuis la version 2.4, vous pouvez aplatir des itérables plus compliqués et imbriqués avec more_itertools.collapse( source , apportée 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]
pylang
la source
En effet. Cela devrait être la réponse acceptée
brunetton
Si vous pouvez vous permettre d'ajouter un package à votre projet - cette réponse est la meilleure
viddik13
22

La raison pour laquelle votre fonction n'a pas fonctionné est que l' extension étend un tableau en place et ne le renvoie pas. Vous pouvez toujours renvoyer x de lambda, en utilisant quelque chose comme ceci:

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

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

Igor Krivokon
la source
7
extendest mieux utilisé comme newlist = [], extend = newlist.extend, for sublist in l: extend(l)car il évite les frais généraux de la (plutôt grande) lambdala recherche d'attribut, xet le or.
agf
pour python 3 addfrom 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]
Anil
la source
def flatten(l, a=None): if a is None: a = [][...]
Poik
16

Version récursive

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
la source
1
agréable, aucune importation nécessaire et il est clair de ce qu'il fait ... aplatir une liste, point :)
Goran B.
1
tout simplement génial!
Sachin Sharma
15

matplotlib.cbook.flatten() fonctionnera pour les listes imbriquées même si elles s'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]

C'est 18 fois plus rapide que le soulignement ._. Aplatir:

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
la source
14

La réponse acceptée n'a pas fonctionné pour moi lorsqu'il s'agissait de listes textuelles de longueurs variables. Voici une approche alternative 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']
user9074332
la source
13

Une mauvaise caractéristique 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 []. Ce devrait plutôt être une valeur par défaut. En raison de la façon dont les objets Python fonctionnent, ceux-ci doivent être définis dans la fonction, pas 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]
Deleet
la source
13

Les éléments suivants me semblent les plus simples:

>>> 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]
diable dans les détails
la source
Ne fonctionne pas pour les listes de différentes dimensions. -1
nurub
10

On peut aussi utiliser l' appartement de NumPy :

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

Edit 11/02/2016: ne fonctionne que lorsque les sous-listes ont des dimensions identiques.

mdh
la source
serait-ce la solution optimale?
RetroCode
6

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

A. Attia
la source
Cela fonctionne également pour les listes numériques, les chaînes et mixtes
Nitin
2
Échoue pour les données imbriquées de manière inégale, comme[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

Si vous êtes prêt à abandonner une petite quantité de vitesse pour un look plus propre, vous pouvez utiliser numpy.concatenate().tolist()ou 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

Vous pouvez en savoir plus ici dans les documents numpy.concatenate et numpy.ravel

mkultra
la source
1
Ne fonctionne pas pour les listes imbriquées de manière inégale comme[1, 2, [3], [[4]], [5, [6]]]
EL_DON
5

La solution la plus rapide que j'ai trouvée (pour une grande liste de toute façon):

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

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

Canuck
la source
1
C'est faux, aplatir réduira les dimensions du nd tableau à un, mais ne concaténera pas les listes à l'intérieur comme une seule.
Ando Jurai
5

Code simple pour le underscore.pyventilateur de package

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.pyavec pip

pip install underscore.py
Vu Anh
la source
De même, vous pouvez utiliser pydash . Je trouve que cette version est beaucoup plus lisible que la compréhension de la liste ou toute autre réponse.
gliemezis
2
C'est super lent.
Nico Schlömer
2
Pourquoi a-t-il un module nommé _? Cela semble être un mauvais nom. Voir stackoverflow.com/a/5893946/6605826
EL_DON
2
@EL_DON: À partir de la page du fichier lisez-moi underscore.py "Underscore.py est un port python d'une excellente bibliothèque javascript underscore.js". Je pense que c'est la raison de ce nom. Et oui, ce n'est pas un bon nom pour python
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
la source
Échoue pour python2.7 pour l'exemple de liste imbriquée dans la question:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON
@EL_DON testé sur python 2.7.5. ça marche bien
englealuze
5

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,, 6est juste un scalaire; ce n'est pas itérable, donc les itinéraires ci-dessus échoueront ici.
  • Un élément, 'abc', est techniquement itérables (tousstr s sont). Cependant, en lisant un peu entre les lignes, vous ne voulez pas le traiter comme tel - vous voulez le traiter comme un élément unique.
  • L'élément final [8, [9, 10]]est lui-même un itérable imbriqué. Compréhension de liste de base et chain.from_iterableextraire uniquement "1 niveau vers le bas".

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 avec Iterableun ABC de itertools, mais vous voulez également vous assurer que (2) l'élément n'est pas "semblable à une chaîne".

Brad Solomon
la source
1
Si vous êtes toujours intéressé par la compatibilité avec Python 2, yield fromforfor x in flatten(i): yield x
passez
5
flat_list = []
for i in list_of_list:
    flat_list+=i

Ce code fonctionne également très bien car il étend simplement la liste tout le long. Bien qu'il soit très similaire mais n'en a qu'un pour la boucle. Il a donc moins de complexité que d'ajouter 2 pour les boucles.

Deepak Yadav
la source
5
from nltk import flatten

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

L'avantage de cette solution sur la plupart des autres ici est que si vous avez une liste comme:

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

tandis que la plupart des autres solutions génèrent une erreur, cette solution les gère.

Alijy
la source
La question indique une "liste de listes", mais votre exemple de liste comprend un élément non-liste. La plupart des autres solutions s'en tiennent à la question initiale. Votre solution résout un problème plus large, mais elle nécessite également un package Python non-base (nltk) qui doit être installé en premier.
simonobo
4

Ce n'est peut-être pas le moyen le plus efficace, mais j'ai pensé à mettre une doublure (en fait une doublure). Les deux versions fonctionneront sur des listes imbriquées de hiérarchie arbitraire et exploiteront les fonctionnalités du langage (Python3.5) et la récursivité.

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)

La sortie est

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

Cela fonctionne en profondeur d'abord. La récursivité descend jusqu'à ce qu'il trouve un élément non-liste, puis étend la variable locale flistet puis la restaure au parent. Chaque fois qu'il flistest retourné, il est étendu aux parents flistdans la liste de compréhension. Par conséquent, à la racine, une liste plate est renvoyée.

Celui ci-dessus crée plusieurs listes locales et les renvoie qui sont utilisées pour étendre la liste des parents. Je pense que le moyen de contourner cela peut être de créer un gloabl flist, comme ci-dessous.

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)

La sortie est à nouveau

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

Bien que je ne sois pas sûr pour le moment de l'efficacité.

phoxis
la source
Pourquoi étendre ([l]) au lieu d'ajouter (l)?
Maciek
3

Une autre approche inhabituelle qui fonctionne pour des listes hétéro et homogènes d'entiers:

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
la source
C'est juste une manière plus compliquée et un peu plus lente de ce que ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 a déjà publié auparavant. J'ai réinventé sa proposition hier, donc cette approche semble assez populaire de nos jours;)
Darkonaut
Pas tout à fait: 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
mon code comme un liner serait: flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
tharndt
1
Vous avez en effet raison +1, la proposition de ᴡʜᴀᴄᴋᴀᴍᴀᴅᴏᴏᴅʟᴇ3000 ne fonctionnera pas avec des nombres à plusieurs chiffres, je n'ai pas non plus testé cela avant même si cela devrait être évident. Vous pouvez simplifier votre code et écrire [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. Mais je suggérerais de m'en tenir à la proposition de Deleet pour des cas d'utilisation réels. Il ne contient pas de transformations de type hacky, il est plus rapide et plus polyvalent car il gère également naturellement les listes avec des types mixtes.
Darkonaut
2
Malheureusement non. Mais j'ai vu ce code récemment ici: Python Practice Book 6.1.2
tharndt