Aplanar una lista de listas irregular (anidada arbitrariamente)

8 minutos de lectura

avatar de usuario de telliott99
Telliott99

Sí, sé que este tema se ha tratado antes:

  • ¿Idioma de Python para encadenar (aplanar) un iterable infinito de iterables finitos?
  • Aplanando una lista superficial en Python
  • ¿Comprensión para aplanar una secuencia de secuencias?
  • ¿Cómo hago una lista plana a partir de una lista de listas?

pero hasta donde yo sé, todas las soluciones, excepto una, fallan en una lista como [[[1, 2, 3], [4, 5]], 6]donde la salida deseada es [1, 2, 3, 4, 5, 6] (o quizás incluso mejor, un iterador).

La única solución que vi que funciona para un anidamiento arbitrario se encuentra en esta pregunta:

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

¿Es este el mejor enfoque? ¿Pasé por alto algo? ¿Algún problema?

  • El hecho de que haya tantas respuestas y tanta acción en esta pregunta realmente sugiere que esta debería ser una función integrada en alguna parte, ¿verdad? Es especialmente una lástima que se eliminó compiler.ast de Python 3.0

    – Manoplas

    18 de marzo de 2014 a las 13:22

  • Diría que lo que realmente necesita Python es una recursividad ininterrumpida en lugar de otra función integrada.

    – arcilla

    07/04/2015 a las 23:27

  • @Mittechops: totalmente en desacuerdo, el hecho de que las personas trabajen con API obviamente malas/estructuras de datos demasiado complicadas (solo una nota: listestá destinado a ser homogéneo) no significa que sea culpa de Python y necesitamos una función integrada para tal tarea

    – Azat Ibrakov

    23 de mayo de 2019 a las 9:12


  • Si puede permitirse agregar un paquete a su proyecto, supongo que el más_itertools.collapse la solución lo hará mejor. De esta respuesta: stackoverflow.com/a/40938883/3844376

    – viddik13

    5 de marzo de 2020 a las 15:57


  • @ viddik13: considere hacer que esa sea también una respuesta para esta pregunta. Obtendría absolutamente mi voto a favor. (Estoy de acuerdo con Mittechops.) El hecho de que no sea un incorporado La función está bien (refiriéndose a Azat Ibrakov), pero debería haber (¡y aparentemente hay!) una rutina de biblioteca para hacer esto. (Porque: no todos irregularidad es “malo”/”demasiado complicado”. A veces, es solo… no regular, y eso está bien. EN MI HUMILDE OPINIÓN. siempre y cuando lo que es está bien definida, y puede ser, y aún ser, irregular (“una lista anidada arbitrariamente (de listas (de listas…)) de enteros”, por ejemplo, está bien definida).)

    – Lindes

    7 de febrero de 2021 a las 19:06

Avatar de usuario de Josh Lee
jose lee

Mi solución:

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 poco más conciso, pero más o menos lo mismo.

  • Puede hacer esto sin importar nada si solo try: iter(x) para probar si es iterable… Pero no creo que tener que importar un módulo stdlib sea una desventaja que valga la pena evitar.

    – abarnert

    5 de julio de 2013 a las 8:03

  • Vale la pena señalar que esta solución solo funciona si todos los elementos son del tipo int

    – Nir Alfasi

    7 de diciembre de 2014 a las 6:14

  • Podría hacerlo más conciso, def flatten(x): return [a for i in x for a in flatten(i)] if isinstance(x, collections.Iterable) else [x] – pero la legibilidad puede ser subjetiva aquí.

    – Cero

    25 de julio de 2017 a las 17:13


  • esto no funciona en cadenas porque las cadenas también son iterables. Reemplace la condición con if isinstance(x, collections.Iterable) and not isinstance(x, basestring)

    – aandis

    29 de noviembre de 2018 a las 11:51

  • reemplazar collections.Iterable con list

    – noobninja

    17 de junio de 2020 a las 1:59

  • Gracias, eso funciona bien para Python 3. Para 2.x se necesita lo anterior: for i in flatten(item): yield i

    – dansalmo

    08/09/2015 a las 14:54


  • enumerar(aplanar([[‘X’]’Y’])) falla en la variante 2.X

    – sten

    18 de marzo de 2018 a las 23:06

  • @ user1019129 mira mi comentario sobre el tuyo

    – dansalmo

    20 de marzo de 2018 a las 0:14

  • sí, falla con el ciclo… creo que porque una cadena también es una “matriz” de caracteres

    – sten

    20 de marzo de 2018 a las 20:16

Aquí está mi versión funcional de flatten recursivo que maneja tanto tuplas como listas, y te permite agregar cualquier combinación de argumentos posicionales. Devuelve un generador que produce la secuencia completa en orden, argumento por argumento:

flatten = lambda *n: (e for a in n
    for e in (flatten(*a) if isinstance(a, (tuple, list)) else (a,)))

Uso:

l1 = ['a', ['b', ('c', 'd')]]
l2 = [0, 1, (2, 3), [[4, 5, (6, 7, (8,), [9]), 10]], (11,)]
print list(flatten(l1, -2, -1, l2))
['a', 'b', 'c', 'd', -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

  • gran solución, sin embargo, sería de mucha ayuda si agregara algún comentario para describir qué e, a, n Referirse a

    –Kristof Pal

    20 de noviembre de 2013 a las 14:16

  • @WolfgangKuehne: Intente args por n, intermediate (o el más corto mid o tal vez prefieras element) por a y result por easi que: flatten = lambda *args: (result for mid in args for result in (flatten(*mid) if isinstance(mid, (tuple, list)) else (mid,)))

    –Dennis Williamson

    5 de enero de 2014 a las 21:18

  • Esto es significativamente más rápido que compiler.ast.flatten. Genial, código compacto, funciona para cualquier tipo de objeto (creo).

    – bcdan

    30 de julio de 2015 a las 23:04

  • Esta es la única solución que he encontrado, en una búsqueda moderada en Google, en cualquier sitio web que realmente funcione para listas anidadas a más de un nivel.

    – Alexej Magura

    3 de agosto de 2020 a las 14:56

  • Esto es una obra de arte. Tan pocos personajes, y aún así casi imposible de entender. 10/10 mejores golf de código Python que he visto hasta ahora 🏌️‍♂️🏌️‍♀️⛳️. Tener algo tan corto casi compensa el hecho de que Python no tiene una función de aplanamiento incorporada.

    –Jeff Hykin

    26 de octubre de 2020 a las 2:07


Avatar de usuario de Alex Martelli
alex martelli

Versión del generador de la solución no recursiva de @unutbu, según lo solicitado por @Andrew en un comentario:

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

Versión ligeramente simplificada de este generador:

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)

  • gran solución, sin embargo, sería de mucha ayuda si agregara algún comentario para describir qué e, a, n Referirse a

    –Kristof Pal

    20 de noviembre de 2013 a las 14:16

  • @WolfgangKuehne: Intente args por n, intermediate (o el más corto mid o tal vez prefieras element) por a y result por easi que: flatten = lambda *args: (result for mid in args for result in (flatten(*mid) if isinstance(mid, (tuple, list)) else (mid,)))

    –Dennis Williamson

    5 de enero de 2014 a las 21:18

  • Esto es significativamente más rápido que compiler.ast.flatten. Genial, código compacto, funciona para cualquier tipo de objeto (creo).

    – bcdan

    30 de julio de 2015 a las 23:04

  • Esta es la única solución que he encontrado, en una búsqueda moderada en Google, en cualquier sitio web que realmente funcione para listas anidadas a más de un nivel.

    – Alexej Magura

    3 de agosto de 2020 a las 14:56

  • Esto es una obra de arte. Tan pocos personajes, y aún así casi imposible de entender. 10/10 mejores golf de código Python que he visto hasta ahora 🏌️‍♂️🏌️‍♀️⛳️. Tener algo tan corto casi compensa el hecho de que Python no tiene una función de aplanamiento incorporada.

    –Jeff Hykin

    26 de octubre de 2020 a las 2:07


esta versión de flatten evita el límite de recurrencia de python (y, por lo tanto, funciona con iterables anidados arbitrariamente profundos). Es un generador que puede manejar cadenas e iterables arbitrarios (incluso infinitos).

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

Aquí hay algunos ejemplos que demuestran su uso:

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]

A pesar de que flatten puede manejar generadores infinitos, no puede manejar anidamiento infinito:

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

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

  • ¿Algún consenso sobre si usar ABC Iterable o ABC Sequence?

    – Wim

    15/09/2013 a las 20:09

  • sets, dicts, deques, listiterators, generatorsidentificadores de archivo y clases personalizadas con __iter__ definidas son todas las instancias de collections.Iterablepero no collections.Sequence. El resultado de aplanar un dict es un poco dudoso, pero por lo demás, creo collections.Iterable es un valor predeterminado mejor que collections.Sequence. Definitivamente es el más liberal.

    – unutbu

    15/09/2013 a las 20:16

  • @wim: un problema con el uso collections.Iterable es que esto incluye infinitos generadores. He cambiado mi respuesta manejar este caso.

    – unutbu

    17 de septiembre de 2013 a las 9:27

  • Esto no parece funcionar para los ejemplos 3 y 4. tira StopIteration. Además, parece while True: first = next(remainder) podría ser reemplazado por for first in remainder:.

    – georgia

    23 de mayo de 2019 a las 12:43

  • @Georgy, esto podría solucionarse encapsulando el cuerpo de flatten en un try-except StopIteration block.

    – baduker

    24 de febrero de 2020 a las 9:02

¿Ha sido útil esta solución?