Aplanar listas anidadas en Python

Aplanar listas anidadas en Python

¿Que es una lista anidada?

A veces, cuando trabajas con datos en Python, puedes tener los datos como una lista de listas anidadas. Una operación común es aplanar estos datos en una lista unidimensional.

Aplanar una lista implica convertir una lista multidimensional, como una matriz, en una lista unidimensional.

Para ilustrar mejor lo que significa aplanar una lista, vamos a a ver el siguiente ejemplo:

matriz = [
     [4, 6, 1, 1],
     [5, 6, 2, 5],
     [1, 7, 1, 6],
     [2, 9, 1, 7],
 ]

La variable matriz contiene una lista en Python que a su vez contiene cuatro listas anidadas. Cada lista anidada representa una fila de la matriz y cada fila almacena cuatro elementos.

Aplanar la lista sería obtener el siguiente resultado:

[4, 6, 1, 1, 5, 6, 2, 5, 1, 7, 1, 6, 2, 9, 1, 7]

Como podemos ver, al aplicar una operación sobre la lista, obtenemos una lista unidimensional que contiene todos los valores numéricos de la matriz.

En este post veremos diferentes maneras de obtener este resultado. He ordenado las diferentes soluciones de peor a mejor según mi criterio.

1. Usando functools.reduce

from functools import reduce
from operator import add
lista_plana = reduce(add, matriz, [])
lista_plana

>>> [4, 6, 1, 1, 5, 6, 2, 5, 1, 7, 1, 6, 2, 9, 1, 7]

Esta opción es la peor de todas por que con el uso de reduce siempre obtenemos un peor rendimiento que usando la función built-in de Python sum.

En otras palabras, sum (opción que veremos a continuación) es una versión especializada de reduce que añade datos, así que por rendimiento, la opción de usar reduce quedaría descartada.

Así que esto nos lleva a analizar la siguiente opción.

2. Usando sum

lista_plana = sum(matriz, [])
lista_plana

>>> [4, 6, 1, 1, 5, 6, 2, 5, 1, 7, 1, 6, 2, 9, 1, 7]

Como que he comentado, esta opción es mejor que la anterior pero todavía no es muy eficiente.

La razón principal por la que no es del todo eficiente es por que sum realizará mucho trabajo. ¿Puedes ver por qué?

Cada vez que agregamos dos listas (la acumulada y la matriz), necesitamos crear una tercera lista con todos los elementos de las dos, por lo que se pierde mucho tiempo calculando listas listas.

3. Usando dos bucles anidados

lista_plana = []
for sublista in matriz:
     for elemento in sublista:
         lista_plana.append(elemento)

>>> [4, 6, 1, 1, 5, 6, 2, 5, 1, 7, 1, 6, 2, 9, 1, 7]

¡Esta solución es muy buena! La razón por la que está en tercer lugar no es por que se mala, ¡sino porque se puede hacer aún mejor! 🙂

4. Usando “list comprehension” (muy buena)

lista_plana = [
     elemento
     for sublista in lista_de_listas
     for elemento in sublista
 ]

>>> [4, 6, 1, 1, 5, 6, 2, 5, 1, 7, 1, 6, 2, 9, 1, 7]

Esta solución es mejor que el utilizar dos bucles porque el uso list comprehension tiene muchas ventajas cuando se tienen bucles simples cuyo único trabajo es agregar elementos a una nueva lista.

El uso de list comprehension es la forma “Python” de hacer mejor código, pero la siguiente solución es mejor que usar list comprehension en algunos casos.

5. Usando itertools.chain (mejor opción)

from itertools import chain
lista_plana = list(chain.from_iterable(matriz))

>>> [4, 6, 1, 1, 5, 6, 2, 5, 1, 7, 1, 6, 2, 9, 1, 7]

El en modulo itertools tenemos la función chain que es mejor que el uso de list comprehension cuando no necesitamos obtener una lista final sino que necesitamos obtener un generator.

En el código anterior, se hace uso de list(chain…) únicamente para mostrar la lista resultante. Pero la función chain es un generator, lo que significa que es lazy (perezoso) y es muy útil cuando quieres iterar a través de la lista plana, pero no necesariamente necesitas una lista como tal.

Por lo tanto, esta solución es sólo mejor que list comprehension en aquellos casos en los que queremos la lista final y no obtener un generator.

5. Aplanar una lista con dimensiones heterogéneas

Las cinco opciones que hemos visto sobre aplanar listas asumen que la dimensión de cada lista anidada es constante y siempre 2.

¿Que pasa si tenemos una lista que contiene listas con dimensiones distintas?

¿Como aplanaríamos algo similar a esto?

lista_total = [[[2, 1], [5], [[1, [1, [2, 5, 7]]]], 7], [1, 5], 11]

Es aquí donde podemos hacer uso de una función que use generators recursivos:

def aplanar(obj):
    if isinstance(obj, list):
        for item in obj:
            yield from aplanar(item)
    else:
        yield obj

print(list(aplanar(lista_total)))

>>> [2, 1, 5, 1, 1, 2, 5, 7, 7, 1, 5, 11]

LinkedIn

comments powered by Disqus