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 perdue, mais peut-être existe-t-il un "one-liner"? Je l'ai essayé avec reduction, 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'
Étant donné une liste de listes l
,
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, 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 nécessairement O(L**2)
lorsqu'il existe plusieurs sous-listes - car la liste des résultats intermédiaires s'allonge, à chaque étape, un nouvel objet de liste des 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 des raisons de simplicité et sans perte de généralité, supposons que vous ayez une sous-liste de I articles chacun: les premiers articles sont copiés L-1 fois, les deuxièmes articles L-2 fois, etc. 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 également chaque élément (de son lieu de résidence d'origine à la liste des résultats) également une fois.
Vous pouvez utiliser itertools.chain()
:
>>> import itertools
>>> list2d = [[1,2,3],[4,5,6], [7], [8,9]]
>>> merged = list(itertools.chain(*list2d))
ou, sur Python> = 2.6, utilisez itertools.chain.from_iterable()
qui ne nécessite pas de décompresser la liste:
>>> 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 être aussi plus rapide:
[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
Note de l'auteur: C'est inefficace. Mais amusant, parce que monoids sont géniaux. 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 [1,3]+[2,4]
à la suite de sum([[1,3],[2,4]],[])
, qui est égal à [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.
J'ai testé la plupart des solutions suggérées avec perfplot (un de mes projets favoris, essentiellement un wrapper autour de timeit
)
functools.reduce(operator.iconcat, a, [])
être la solution la plus rapide. (operator.iadd
est également rapide.)
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)],
logx=True,
logy=True,
xlabel='num lists'
)
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 (ce 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]
Je prends ma déclaration en arrière. la somme n'est pas le gagnant. Bien que ce soit plus rapide quand la liste est petite. Mais la performance se dégrade de manière significative avec des listes plus grandes.
>>> 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 le traitement n'est pas encore terminé!
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 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
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 transtyper le résultat en une variable list
ou l'exécuter explicitement.
Pour n'aplatir qu'un seul niveau et si chacun des éléments est lui-même itératif, vous pouvez également utiliser iteration_utilities.flatten
qui n'est lui-même 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 permettant de prendre en compte la vaste gamme de valeurs couvertes. Pour un raisonnement qualitatif: plus c'est bas, mieux c'est.
Les résultats montrent que si l'iterable ne contient que quelques iterables internes, alors sum
sera le plus rapide. Cependant, pour de longues iterables, seuls le itertools.chain.from_iterable
, le iteration_utilities.deepflatten
ou la compréhension imbriquée ont des performances raisonnables, itertools.chain.from_iterable
étant le plus rapide (comme le remarquait déjà 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
Pourquoi utilisez-vous prolonger?
reduce(lambda x, y: x+y, l)
Cela devrait bien fonctionner.
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:
>>> 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 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
Pensez à installer le paquet more_itertools
.
> pip install more_itertools
Il est livré avec une implémentation pour flatten
( source , à partir des recettes de 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 itérables imbriqués plus complexes et plus compliqués 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]
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.
>>> 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))
...nipath:
>>> from unipath.path import flatten
>>> list(flatten(l))
...Setuptools:
>>> from setuptools.namespaces import flatten
>>> list(flatten(l))
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]
Une des mauvaises caractéristiques 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]
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]
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
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']]
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']
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']
On peut aussi utiliser NumPy's flat :
import numpy as np
list(np.array(l).flat)
Éditer 11/02/2016: Ne fonctionne que lorsque les sous-listes ont des dimensions identiques.
Code simple pour underscore.py
package fan
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
def flatten(alist):
if alist == []:
return []
Elif type(alist) is not list:
return [alist]
else:
return flatten(alist[0]) + flatten(alist[1:])
Une autre approche inhabituelle qui fonctionne pour les listes d’entiers hétérogènes et homogènes:
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(',')]
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)
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.
Si vous êtes prêt à abandonner une petite quantité de vitesse pour un look plus net, 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 la documentation numpy.concatenate et numpy.ravel
Une méthode récursive simple utilisant reduce
de functools
et l'opérateur add
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 flatten
prend en paramètre lst
. Il boucle tous les éléments de lst
jusqu'à atteindre des nombres entiers (peut également changer int
en float
, str
, etc. pour d'autres types de données), qui sont ajoutés à la valeur de retour de la récursivité la plus externe.
La récursivité, contrairement aux méthodes telles que les boucles for
et les monades, est qu’il s’agit de une solution générale non limitée par la liste profondeur . Par exemple, une liste de profondeur 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]
Je suis récemment tombé sur une situation où j’avais un mélange de chaînes et de données numériques dans des sous-listes telles que
test = ['591212948',
['special', 'assoc', 'of', 'Chicago', 'Jon', 'Doe'],
['Jon'],
['Doe'],
['fl'],
92001,
555555555,
'hello',
['hello2', 'a'],
'b',
['hello33', ['z', 'w'], 'b']]
où les méthodes telles que flat_list = [item for sublist in test for item in sublist]
n'ont pas fonctionné. Donc, je suis venu avec la solution suivante pour 1+ niveau de sous-listes
def concatList(data):
results = []
for rec in data:
if type(rec) == list:
results += rec
results = concatList(results)
else:
results.append(rec)
return results
Et le résultat
In [38]: concatList(test)
Out[38]:
Out[60]:
['591212948',
'special',
'assoc',
'of',
'Chicago',
'Jon',
'Doe',
'Jon',
'Doe',
'fl',
92001,
555555555,
'hello',
'hello2',
'a',
'b',
'hello33',
'z',
'w',
'b']
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 dans une 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 d'une manière d'abord en profondeur. La récursivité diminue jusqu'à ce qu'elle trouve un élément non-list, puis étend la variable locale flist
et la restaure ensuite au parent. Chaque fois que flist
est renvoyé, il est étendu à la variable flist
du parent dans la liste de compréhension. Par conséquent, à la racine, une liste non hiérarchique est renvoyée.
Le précédent crée plusieurs listes locales et les renvoie, qui servent à étendre la liste du parent. Je pense que la solution pourrait être de créer une flist
globale, 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é.
Note: La version ci-dessous s'applique à Python 3.3+ car il utilise yield_from
. six
est é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 présentées 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:
6
, est juste un scalaire; ce n'est pas itérable, donc les routes ci-dessus vont échouer ici.'abc'
, est techniquement itérable (tous les str
s 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.[8, [9, 10]]
, est lui-même un élément imbriqué imbriqué. Compréhension de base des listes et chain.from_iterable
n'extrayez que "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 avec Iterable
, un ABC de itertools
, mais vous voulez également vous assurer que (2) l’élément est non "comme une chaîne. "
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)))
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]
Jeter mon chapeau sur le ring ...
B = [ [...], [...], ... ]
A = []
for i in B:
A.extend(i)
Cela peut être fait en utilisant toolz.concat
ou cytoolz.concat
(version cythonisée, qui pourrait être plus rapide dans certains cas):
from cytoolz import concat
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(concat(l)) # or just `concat(l)` if one only wants to iterate over the items
Sur mon ordinateur, en python 3.6, le temps semble presque aussi rapide que [item for sublist in l for item in sublist]
(sans compter le temps d'importation):
In [611]: %timeit L = [item for sublist in l for item in sublist]
695 ns ± 2.75 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [612]: %timeit L = [item for sublist in l for item in sublist]
701 ns ± 5.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [613]: %timeit L = list(concat(l))
719 ns ± 12 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [614]: %timeit L = list(concat(l))
719 ns ± 22.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
La version toolz
est en effet plus lente:
In [618]: from toolz import concat
In [619]: %timeit L = list(concat(l))
845 ns ± 29 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [620]: %timeit L = list(concat(l))
833 ns ± 8.73 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
La question demandait une réponse géniale "one-liner". Ensuite, voici ma contribution en clair Python:
a = [[2,3,6],[False,'foo','bar','baz'],[3.1415],[],[0,0,'0']]
flat_a = [a[i][j] for i in range(len(a)) for j in range(len(a[i]))]
flat_a
[2, 3, 6, False, 'foo', 'bar', 'baz', 3.1415, 0, 0, '0']
Ma solution (intuitive et courte):
def unnest(lists):
unnested = []
for l in lists:
unnested = unnested + l
return unnested
Vous pouvez utiliser Dask pour aplatir/fusionner une liste, avec l'avantage supplémentaire de la conservation de la mémoire. Dask utilise la mémoire différée, qui ne s'active qu'avec compute (). Vous pouvez donc utiliser l'API Dask Bag sur votre liste, puis la calculer () à la fin. Documentation: http://docs.dask.org/en/latest/bag-api.html
import dask.bag as db
my_list = [[1,2,3],[4,5,6],[7,8,9]]
my_list = db.from_sequence(my_list, npartitions = 1)
my_list = my_list.flatten().compute()
# [1,2,3,4,5,6,7,8,9]
Si vous souhaitez prendre un itinéraire plus lent (durée d'exécution), vous devriez aller plus loin que la vérification par rapport à la chaîne. Vous pourriez avoir une liste imbriquée de nuplets ou un nuplet de listes ou une séquence de types itérables définis par l'utilisateur.
Prenez un type de la séquence d'entrée et vérifiez par rapport à cela.
def iter_items(seq):
"""Yield items in a sequence."""
input_type = type(seq)
def items(subsequence):
if type(subsequence) != input_type:
yield subsequence
else:
for sub in subsequence:
yield from items(sub)
yield from items(seq)
>>> list(iter_items([(1, 2), [3, 4], "abc", [5, 6], [[[[[7]]]]]]))
[(1, 2), 3, 4, 'abc', 5, 6, 7]
Exemple nettoyé @ Deleet
from collections import Iterable
def flatten(l, a=[]):
for i in l:
if isinstance(i, Iterable):
flatten(i, a)
else:
a.append(i)
return a
daList = [[1,4],[5,6],[23,22,234,2],[2], [ [[1,2],[1,2]],[[11,2],[11,22]] ] ]
print(flatten(daList))
Exemple: https://repl.it/G8mb/0
Cela fonctionne avec des listes non imbriquées. Il peut être facilement étendu pour fonctionner avec d'autres types de iterables.
def flatten(seq):
"""list -> list
return a flattend list from an abitrarily nested list
"""
if not seq:
return seq
if not isinstance(seq[0], list):
return [seq[0]] + flatten(seq[1:])
return flatten(seq[0]) + flatten(seq[1:])
Échantillon échantillon
>>> flatten([1, [2, 3], [[[4, 5, 6], 7], [[8]]], 9])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Voici une fonction utilisant la récursivité qui fonctionnera sur toute liste imbriquée arbitraire.
def flatten(nested_lst):
""" Return a list after transforming the inner lists
so that it's a 1-D list.
>>> flatten([[[],["a"],"a"],[["ab"],[],"abc"]])
['a', 'a', 'ab', 'abc']
"""
if not isinstance(nested_lst, list):
return(nested_lst)
res = []
for l in nested_lst:
if not isinstance(l, list):
res += [l]
else:
res += flatten(l)
return(res)
>>> flatten([[[],["a"],"a"],[["ab"],[],"abc"]])
['a', 'a', 'ab', 'abc']
une autre façon amusante de faire ceci:
from functools import reduce
from operator import add
li=[[1,2],[3,4]]
x= reduce(add, li)
Nous pouvons faire la même chose en utilisant les concepts de base de python.
nested_list=[10,20,[30,40,[50]],[80,[10,[20]],90],60]
flat_list=[]
def unpack(list1):
for item in list1:
try:
len(item)
unpack(item)
except:
flat_list.append(item)
unpack(nested_list)
print (flat_list)