Reemplazo de Python 3 por la función obsoleta compiler.ast aplana
python-3.x flatten (5)
¿Cuál es la forma recomendada de aplanar las listas anidadas desde la desaprobación del paquete del compilador ?
>>> from compiler.ast import flatten
>>> flatten(["junk",["nested stuff"],[],[[]]])
[''junk'', ''nested stuff'']
Sé que hay algunas respuestas de desbordamiento de la pila para el aplanamiento de la lista, pero estoy esperando que el paquete estándar pitónico, "uno, y preferiblemente solo uno, forma obvia" de hacer esto.
Mi fea solución de while-chain
, solo por diversión:
from collections import Iterable
from itertools import chain
def flatten3(seq, exclude=(str,)):
sub = iter(seq)
try:
while sub:
while True:
j = next(sub)
if not isinstance(j, Iterable) or isinstance(j, exclude):
yield j
else:
sub = chain(j, sub)
break
except StopIteration:
return
No hay un método incorporado para una lista con anidamiento arbitrario, pero algo como esto ...
def flatten(l):
for i in l:
if isinstance(i, (list, tuple)):
for ii in flatten(i):
yield ii
else:
yield i
>>> l = ["junk",["nested stuff"],[],[[]]]
>>> list(flatten(l))
[''junk'', ''nested stuff'']
... funcionará para listas y tuplas. Si desea admitir cualquier objeto que pueda utilizar en un for item in object
expresión de for item in object
, entonces probablemente sea mejor usar pato-tipado así ...
def flatten(l):
for i in l:
if isinstance(i, (str, bytes)):
yield i
else:
try:
for ii in flatten(i):
yield ii
except TypeError:
yield i
>>> l = ["junk",["nested stuff"],[],[[]]]
>>> list(flatten(l))
[''junk'', ''nested stuff'']
... que es un poco más robusto que verificar isinstance(el, Iterable)
, ya que no será compatible con algunos casos, como este ...
class Range10:
def __getitem__(self, index):
if index >= 10:
raise IndexError
return index
>>> import collections
>>> r10 = Range10()
>>> isinstance(r10, collections.Iterable)
False
>>> list(Range10())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
itertools.chain
es la mejor solución para aplanar cualquier nivel iterable anidado - es altamente eficiente en comparación con cualquier solución de python puro.
Dicho esto, funcionará en todos los iterables, por lo que es necesario realizar algunas comprobaciones si quiere evitar que aplana cadenas, por ejemplo.
Del mismo modo, no se aplanará mágicamente a una profundidad arbitraria. Dicho esto, en general, no se requiere una solución genérica de este tipo; en su lugar, es mejor mantener sus datos estructurados para que no requieran un aplanamiento de esa manera.
Editar: Yo diría que si uno tiene que hacer un aplanamiento arbitrario, esta es la mejor manera:
import collections
def flatten(iterable):
for el in iterable:
if isinstance(el, collections.Iterable) and not isinstance(el, str):
yield from flatten(el)
else:
yield el
Recuerde usar la basestring
en 2.x sobre str
, y for subel in flatten(el): yield el
lugar de yield from flatten(el)
pre-3.3.
Como se señaló en los comentarios, yo diría que esta es la opción nuclear, y es probable que cause más problemas de los que resuelve. En cambio, la mejor idea es hacer que su salida sea más regular (salida que contiene un elemento que todavía le da como una tupla de un elemento, por ejemplo), y hacer un aplanamiento regular en un nivel donde se introduce, en lugar de hacerlo al final.
Esto producirá un código más lógico, legible y fácil de trabajar. Naturalmente, hay casos en los que necesita hacer este tipo de aplanamiento (si los datos provienen de un lugar con el que no se puede meter, por lo que no tiene más opción que tomarlos en el formato estructurado deficientemente), en cuyo caso, este tipo de solución podría ser necesaria, pero en general, probablemente sea una mala idea.
Puede usar la función aplanar de la biblioteca funky :
from funcy import flatten, isa
flat_list = flatten(your_list)
También puede especificar explícitamente qué valores seguir:
# Follow only sets
flat_list = flatten(your_list, follow=isa(set))
Echa un vistazo a su implementación si quieres un algoritmo.
Su función declarada toma una lista anidada y la aplana en una nueva lista.
Para aplanar una lista anidada arbitrariamente en una nueva lista, esto funciona en Python 3 como espera:
import collections
def flatten(x):
result = []
for el in x:
if isinstance(x, collections.Iterable) and not isinstance(el, str):
result.extend(flatten(el))
else:
result.append(el)
return result
print(flatten(["junk",["nested stuff"],[],[[]]]))
Huellas dactilares:
[''junk'', ''nested stuff'']
Si quieres un generador que haga lo mismo:
def flat_gen(x):
def iselement(e):
return not(isinstance(e, collections.Iterable) and not isinstance(e, str))
for el in x:
if iselement(el):
yield el
else:
for sub in flat_gen(el): yield sub
print(list(flat_gen(["junk",["nested stuff"],[],[[[],[''deep'']]]])))
# [''junk'', ''nested stuff'', ''deep'']
Para Python 3.3 y posterior, use yield en lugar del bucle:
def flat_gen(x):
def iselement(e):
return not(isinstance(e, collections.Iterable) and not isinstance(e, str))
for el in x:
if iselement(el):
yield el
else:
yield from flat_gen(el)