557 votes

Aplatir une liste irrégulière de listes

Oui, je sais que ce sujet a déjà été abordé ( aquí , aquí , aquí , aquí ), mais pour autant que je sache, toutes les solutions, sauf une, échouent sur une liste comme celle-ci :

L = [[[1, 2, 3], [4, 5]], 6]

Lorsque la sortie souhaitée est

[1, 2, 3, 4, 5, 6]

Ou, mieux encore, un itérateur. La seule solution que j'ai vue qui fonctionne pour une imbrication arbitraire se trouve dans cette question :

def flatten(x):
    result = []
    for el in x:
        if hasattr(el, "__iter__") and not isinstance(el, basestring):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

flatten(L)

Est-ce le meilleur modèle ? Ai-je oublié quelque chose ? Y a-t-il des problèmes ?

34 votes

Le fait qu'il y ait autant de réponses et autant d'actions sur cette question suggère vraiment que cela devrait être une fonction intégrée quelque part, non ? Il est particulièrement dommage que compiler.ast ait été supprimé de Python 3.0.

3 votes

Je dirais que ce dont Python a vraiment besoin, c'est d'une récursion ininterrompue plutôt que d'un autre buildin.

4 votes

@Mittenchops : totalement en désaccord, le fait que les personnes travaillant avec des API manifestement mauvaises/des structures de données trop compliquées (juste une note : list est censé être homogène) ne signifie pas que c'est la faute de Python et que nous avons besoin d'un buildin pour cette tâche.

476voto

Cristian Points 10133

L'utilisation de fonctions de générateur peut rendre votre exemple un peu plus facile à lire et probablement améliorer les performances.

Python 2

def flatten(l):
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, basestring):
            for sub in flatten(el):
                yield sub
        else:
            yield el

J'ai utilisé le Iterable ABC ajouté en 2.6.

Python 3

Dans Python 3, la fonction basestring n'est plus, mais vous pouvez utiliser un tuple de str y bytes pour obtenir le même effet à cet endroit.

El yield from L'opérateur renvoie un élément d'un générateur, un par un. Ce site syntaxe pour déléguer à un sous-générateur a été ajouté en 3.3

from collections.abc import Iterable

def flatten(l):
    for el in l:
        if isinstance(el, Iterable) and not isinstance(el, (str, bytes)):
            yield from flatten(el)
        else:
            yield el

10 votes

De toutes les suggestions de cette page, c'est la seule qui a aplati cette liste. l = ([[chr(i),chr(i-32)] for i in xrange(ord('a'), ord('z')+1)] + range(0,9)) en un clin d'oeil quand j'ai fait ça list(flatten(l)) . Tous les autres, commençaient à travailler et prenaient une éternité !

8 votes

Cela permet également d'aplatir les dictionnaires. Vous voulez peut-être utiliser collections.Sequence au lieu de collections.Iteratable ?

3 votes

Cela ne fonctionne pas avec les choses qui ne sont pas des listes au départ, par ex. for i in flatten(42): print (i) . Cela pourrait être corrigé en déplaçant le isinstance -et la clause else à l'extérieur de l'échantillon. for el -boucle. (Vous pourriez alors lui envoyer n'importe quoi, et il en ferait une liste aplatie).

68voto

Josh Lee Points 53741

Ma solution :

import collections

def flatten(x):
    if isinstance(x, collections.Iterable):
        return [a for i in x for a in flatten(i)]
    else:
        return [x]

Un peu plus concis, mais à peu près pareil.

7 votes

Vous pouvez le faire sans importer quoi que ce soit si vous voulez juste try: iter(x) pour tester s'il est itérable Mais je ne pense pas que devoir importer un module stdlib soit un inconvénient à éviter.

8 votes

Il convient de noter que cette solution ne fonctionne que si tous les éléments sont de type int

3 votes

Il pourrait être plus concis, def flatten(x): return [a for i in x for a in flatten(i)] if isinstance(x, collections.Iterable) else [x] - mais la lisibilité peut être subjective ici.

51voto

benselme Points 569

Vous pourriez simplement utiliser la fonction flatten dans le fichier compiler.ast module.

>>> from compiler.ast import flatten
>>> flatten([0, [1, 2], [3, 4, [5, 6]], 7])
[0, 1, 2, 3, 4, 5, 6, 7]

36voto

Alex Martelli Points 330805

Version génératrice de la solution non-récursive de @unutbu, comme demandé par @Andrew dans un commentaire :

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    i = 0
    while i < len(l):
        while isinstance(l[i], ltypes):
            if not l[i]:
                l.pop(i)
                i -= 1
                break
            else:
                l[i:i + 1] = l[i]
        yield l[i]
        i += 1

Version légèrement simplifiée de ce générateur :

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    while l:
        while l and isinstance(l[0], ltypes):
            l[0:1] = l[0]
        if l: yield l.pop(0)

2 votes

Il s'agit d'une traversée en pré-ordre de l'arbre formé par les listes imbriquées. seules les feuilles sont retournées. Notez que cette implémentation va consommer la structure de données originale, pour le meilleur ou pour le pire. Il pourrait être amusant d'en écrire une qui préserve l'arbre original, mais qui n'a pas besoin de copier les entrées de la liste.

7 votes

Je pense que vous devez tester les chaînes de caractères -- par exemple, ajoutez "and not isinstance(l[0], basestring)" comme dans la solution de Cristian. Sinon, on obtient une boucle infinie autour de l[0:1] = l[0].

0 votes

C'est un bon exemple de création d'un générateur, mais comme le mentionne c-urchin, l'algorithme lui-même échoue lorsque la séquence contient des chaînes de caractères.

33voto

unutbu Points 222216

Cette version de flatten évite la limite de récursion de python (et fonctionne donc avec des itérables imbriqués et arbitrairement profonds). C'est un générateur qui peut gérer des chaînes de caractères et des itérables arbitraires (même infinis).

import itertools as IT
import collections

def flatten(iterable, ltypes=collections.Iterable):
    remainder = iter(iterable)
    while True:
        first = next(remainder)
        if isinstance(first, ltypes) and not isinstance(first, (str, bytes)):
            remainder = IT.chain(first, remainder)
        else:
            yield first

Voici quelques exemples démontrant son utilisation :

print(list(IT.islice(flatten(IT.repeat(1)),10)))
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

print(list(IT.islice(flatten(IT.chain(IT.repeat(2,3),
                                       {10,20,30},
                                       'foo bar'.split(),
                                       IT.repeat(1),)),10)))
# [2, 2, 2, 10, 20, 30, 'foo', 'bar', 1, 1]

print(list(flatten([[1,2,[3,4]]])))
# [1, 2, 3, 4]

seq = ([[chr(i),chr(i-32)] for i in range(ord('a'), ord('z')+1)] + list(range(0,9)))
print(list(flatten(seq)))
# ['a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F', 'g', 'G', 'h', 'H',
# 'i', 'I', 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N', 'o', 'O', 'p', 'P',
# 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T', 'u', 'U', 'v', 'V', 'w', 'W', 'x', 'X',
# 'y', 'Y', 'z', 'Z', 0, 1, 2, 3, 4, 5, 6, 7, 8]

Bien que flatten peut gérer les générateurs infinis, mais pas l'imbrication infinie :

def infinitely_nested():
    while True:
        yield IT.chain(infinitely_nested(), IT.repeat(1))

print(list(IT.islice(flatten(infinitely_nested()), 10)))
# hangs

1 votes

Y a-t-il un consensus sur le fait d'utiliser ABC Iterable ou ABC Sequence ?

0 votes

sets , dicts , deques , listiterators , generators les gestionnaires de fichiers et les classes personnalisées avec __iter__ définies sont toutes des instances de collections.Iterable mais pas collections.Sequence . Le résultat de l'aplatissement d'un dict est un peu douteux, mais sinon, je pense que collections.Iterable est un meilleur défaut que collections.Sequence . C'est définitivement le plus libéral.

0 votes

@wim : Un problème avec l'utilisation de collections.Iterable est que cela inclut les générateurs infinis. J'ai modifié ma réponse pour gérer ce cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X