En Python, existen varias maneras de inicializar arreglos bidimensionales (listas de listas). Dos formas comunes son:
# Opción 1: Usando multiplicación de listas y comprensión de listas
dp_opcion1 = [[0] * n for _ in range(m)]
# Opción 2: Usando doble comprensión de listas
dp_opcion2 = [[0 for _ in range(n)] for _ in range(m)]
Ambas construcciones resultan en un arreglo de m filas y n columnas, con todos los elementos inicializados a 0. Sin embargo, su comportamiento difiere significativamente cuando se inicializan con objetos mutables.
Equivalencia en Casos Simples
Cuando los elementos a inicializar son objetos inmutables (como enteros, flotantes, strings o None), ambas técnicas son funcionalmente idénticas:
m, n = 3, 4
# Método 1
matriz_a = [[0] * n for _ in range(m)]
# Método 2
matriz_b = [[0 for _ in range(n)] for _ in range(m)]
print(matriz_a == matriz_b) # Salida: True
Diferencias en la Implementación Interna
- Opción 1 (
[[valor] * n for _ in range(m)]): Primero crea una lista conncopias delvalor. Luego, la comprensión de listas creamreferencias a esta misma lista interna. Esto significa que cada "fila" en realidad apunta al mismo objeto de lista en memoria. - Opción 2 (
[[valor for _ in range(n)] for _ in range(m)]): La comprensión de listas externa iteramveces. En cada iteración, la comprensión de listas interna crea una nueva lista independiente conncopias delvalor. Cada fila es un objeto de lista distinto.
El Problema con Objetos Mutables
La distinción se vuelve crucial al inicializar con objetos mutables (como listas vacías [], diccionarios {}, u otros objetos personalizados):
m, n = 3, 4
# Opción 1 - ¡Peligro! Crea referencias compartidas
matriz_peligrosa = [[[]] * n for _ in range(m)]
matriz_peligrosa[0][0].append(1) # Modificamos la primera sublista de la primera fila
print(matriz_peligrosa)
# Salida esperada (incorrecta):
# [[[1], [1], [1], [1]],
# [[1], [1], [1], [1]],
# [[1], [1], [1], [1]]]
# ¡Todas las filas se ven afectadas porque comparten la misma sublista!
# Opción 2 - La forma correcta
matriz_segura = [[[] for _ in range(n)] for _ in range(m)]
matriz_segura[0][0].append(1) # Modificamos la primera sublista de la primera fila
print(matriz_segura)
# Salida correcta:
# [[[1], [], [], []],
# [[], [], [], []],
# [[], [], [], []]]
# Solo la sublista especificada se modifica.
Consideraciones de Rendimiento
Para inicializar con valores inmutables como 0:
- La Opción 1 suele ser ligerramente más rápida. La operación
[0] * nestá optimizada a nivel de C en la implementación de Python. - La Opción 2 es más genarel pero introduce una pequeña sobrecarga debido a la creación explícita de cada elemento.
Ejemplo de comparación de tiempo (puede variar):
import timeit
m, n = 1000, 1000
def inicializar_metodo1():
return [[0] * n for _ in range(m)]
def inicializar_metodo2():
return [[0 for _ in range(n)] for _ in range(m)]
# Ejecutar 10 veces para obtener una medida más estable
tiempo_metodo1 = timeit.timeit(inicializar_metodo1, number=10)
tiempo_metodo2 = timeit.timeit(inicializar_metodo2, number=10)
print(f"Tiempo Método 1: {tiempo_metodo1:.4f} segundos")
print(f"Tiempo Método 2: {tiempo_metodo2:.4f} segundos")
Recomendaciones
- Para objetos inmutables (
0,None, etc.): Ambas opciones son válidas. Si la micro-optimización es crítica,[[valor] * n for _ in range(m)]puede ofrecer una ligera ventaja. - Para objetos mutables (
[],{}, etc.): Utilice siempre la doble comprensión de listas ([[valor for _ in range(n)] for _ in range(m)]) para asegurar que cada sub-objeto sea independiente y evitar efectos secundarios inesperados. - Legibilidad: Si trabaja en un equipo, la doble comprensión de listas puede ser más explícita y fácil de entender para quienes no están familiarizados con el comportamiento de la multiplicación de listas con objetos mutables.