Comparativa de Inicialización de Arreglos Bidimensionales en Python

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 con n copias del valor. Luego, la comprensión de listas crea m referencias 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 itera m veces. En cada iteración, la comprensión de listas interna crea una nueva lista independiente con n copias del valor. 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] * n está 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.

Etiquetas: Python arreglos bidimensionales listas inicialización objetos mutables

Publicado el 8-11 01:03