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

17 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

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

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

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

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