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 dans une boucle for, 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'
3167
Emma 5 juin 2009 à 00:30

28 réponses

Meilleure réponse

É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 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 dans sum) sont, par nécessité, O(L**2) quand il y a des sous-listes L - car la liste de résultats intermédiaire ne cesse de s'allonger, à à chaque étape, un nouvel objet de liste de résultats intermédiaires 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 plus de simplicité et sans perte réelle de généralité, disons que vous avez L sous-listes de chacun des éléments I: les premiers éléments I sont copiés d'avant en arrière L-1 fois, les seconds éléments I 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-à-dire I * (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 de résultats) également exactement une fois.

4490
user1854182 29 mars 2019 à 10:29

La raison pour laquelle votre fonction n'a pas fonctionné est que extend étend un tableau sur 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.

22
Shannon Kelly 19 nov. 2019 à 16:26

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]
15
Saurabh Singh 14 déc. 2018 à 10:51

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
15
EL_DON 20 juil. 2018 à 18:16

Pensez à installer le more_itertools package.

> pip install more_itertools

Il est livré avec une implémentation pour flatten (source, à partir de la 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 imbriqués plus compliqués avec { {X0}} ( source , contribution d'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]
26
pylang 13 juil. 2018 à 21:06

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 des listes plus importantes.

>>> 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 de somme fonctionne toujours pendant 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
38
devsaw 23 déc. 2013 à 09:14

Pourquoi utilisez-vous extend?

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

Cela devrait bien fonctionner.

32
MERose 31 déc. 2014 à 12:07

J'ai testé la plupart des solutions suggérées avec perfplot (un de mes projets favoris, essentiellement un wrapper autour de timeit ), et trouvé

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

Pour être la solution la plus rapide. (operator.iadd est tout aussi rapide.)

enter image description here


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,
    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'
    )
399
Nico Schlömer 5 déc. 2019 à 12:21

Note de l'auteur : c'est inefficace. Mais amusant, car les monoides 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 deuxième argument comme la valeur initiale de la somme (s'il n'est pas donné, 0 est utilisé à la place et ce cas vous donnera une erreur).

Étant donné que vous additionnez des listes imbriquées, vous obtenez réellement [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.

856
jorijnsmit 29 déc. 2018 à 16:00

On peut également utiliser le flat de NumPy

mdh 2 nov. 2016 à 09:59

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]
11
devil in the detail 5 juil. 2017 à 05:14

Il semble y avoir une confusion avec operator.add! Lorsque vous ajoutez deux listes ensemble, le terme correct pour cela est concat, pas ajouter. operator.concat est 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_iterable est assez rapide! Mais ce n'est pas une comparaison à 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
36
TrebledJ 9 mai 2019 à 04:33
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.

5
Alijy 30 sept. 2019 à 10:49

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 flist, puis la restitue au parent. Chaque fois que flist est renvoyé, il est étendu à flist du parent dans la compréhension de la liste. 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 flist gloabl, 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é.

4
phoxis 20 mai 2018 à 08:47

Une autre façon amusante de le faire:

from functools import reduce
from operator import add

li=[[1,2],[3,4]]
x= reduce(add, li)
5
sudeepgupta90 28 août 2019 à 08:09
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])
6
englealuze 8 août 2017 à 14:59

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

6
A. Attia 24 juil. 2018 à 09:11
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]
17
Guillaume Jacquenot 28 nov. 2016 à 08:48

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() qui ne le fait pas nécessite de décompresser 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))

Cette approche est sans doute plus lisible que [item for sublist in l for item in sublist] et semble également plus rapide:

$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;import itertools' 'list(itertools.chain.from_iterable(l))'
20000 loops, best of 5: 10.8 usec per loop
$ python3 -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 5: 21.7 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 5: 258 usec per loop
$ python3 -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99;from functools import reduce' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 5: 292 usec per loop
$ python3 --version
Python 3.7.5rc1
1475
Boris 24 nov. 2019 à 23:53

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)

4
Canuck 28 nov. 2016 à 21:09

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']
14
user9074332 12 oct. 2018 à 01:32

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 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]
13
Deleet 29 nov. 2016 à 03:45

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))
64
Max Malysh 2 août 2019 à 19:30

Si vous souhaitez aplatir une structure de données dont 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]

Il s'agit d'un générateur, vous devez donc convertir le résultat en list ou le répéter explicitement.


Pour aplatir 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 lui-même qu'une mince enveloppe 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és sur la réponse de Nico Schlömer qui n'incluait pas la fonction présentée dans cette réponse):

enter image description here

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

Les résultats montrent que si l'itérable ne contient que quelques itérables internes, alors sum sera le plus rapide, mais pour de longues itérables, seuls les itertools.chain.from_iterable, iteration_utilities.deepflatten ou la compréhension imbriquée ont des performances raisonnables avec {{ X3}} étant le plus rapide (comme l'a déjà remarqué 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

44
MSeifert 18 avril 2018 à 21:27
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 méthode extend() de votre exemple modifie x au lieu de renvoyer une valeur utile (que reduce() attend).

Un moyen plus rapide de faire la version reduce 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]
172
Sanjay Verma 2 janv. 2018 à 05:02

Code simple pour underscore.py fan 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.py avec pip

pip install underscore.py
5
Vu Anh 25 mars 2017 à 05:09
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.

5
Deepak Yadav 20 juin 2018 à 11:12
952914