Clase 8¶
¿Por qué necesitamos más de una dimensión?¶
Las listas permiten almacenar una colección de datos en una sola variable. Pero hay situaciones donde los datos no se organizan en una fila, sino en una tabla: filas y columnas.
Suponga que se quieren guardar las notas de tres estudiantes en cuatro materias distintas:
| Estudiante | Matemática | Español | Ciencias | Historia |
|---|---|---|---|---|
| Ana | 92 | 85 | 78 | 90 |
| Luis | 74 | 68 | 81 | 72 |
| Sofía | 88 | 95 | 91 | 87 |
Con listas separadas, el código quedaría así:
¿Qué pasa cuando los datos crecen?
Con treinta estudiantes y seis materias, habría que declarar treinta listas por separado. Cualquier operación sobre todos los datos requeriría repetir el mismo código treinta veces.
Lo que se necesita es una estructura que agrupe todas las filas en una sola variable y permita recorrerlas de forma sistemática. Esa estructura es la matriz.
¿Qué es una matriz?¶
Una matriz es una lista de listas. Cada elemento de la lista exterior representa una fila, y cada elemento dentro de esa fila representa una columna.
En Python, la tabla de notas anterior se puede representar así:
- Fila 0 — notas de Ana.
- Fila 1 — notas de Luis.
- Fila 2 — notas de Sofía.
Ahora todos los datos viven en una sola variable. Para acceder a un elemento específico se usan dos índices: primero la fila y luego la columna.
- Fila 0, columna 2 →
78(nota de Ana en Ciencias). - Fila 2, columna 1 →
95(nota de Sofía en Español).
La notación matriz[i][j] se lee: fila i, columna j.
Las filas no tienen que ser del mismo tamaño
Python no obliga a que todas las filas tengan la misma cantidad de elementos, pero usualmente se diseña para que sea así, pues resulta más fácil recorrerlos con for loops.
Declaración e inicialización¶
Manual¶
La forma más directa de crear una matriz es escribir todos sus valores explícitamente, fila por fila:
Con ciclos¶
Cuando la matriz es grande o sus valores siguen un patrón, es más práctico generarla con ciclos. El caso más común es crear una matriz vacía (rellena de ceros) para luego llenarla con datos:
filas = 3
columnas = 4
matriz = []
for i in range(filas): # (1)!
fila = []
for j in range(columnas): # (2)!
fila.append(0)
matriz.append(fila) # (3)!
- El ciclo exterior recorre cada fila que se va a crear.
- El ciclo interior llena esa fila con el valor inicial (en este caso,
0). - La fila completa se agrega a la matriz.
El resultado es una matriz de 3 filas y 4 columnas, con todos sus elementos en cero:
Trampa común al inicializar matrices
Puede parecer tentador usar multiplicación de listas para crear la matriz:
- Esto parece crear 3 filas independientes, pero en realidad crea 3 referencias a la misma lista. Modificar un elemento cambia la misma fila en las tres posiciones.
La forma correcta siempre es usar el doble ciclo, que garantiza que cada fila sea una lista independiente.
Acceso y modificación de elementos¶
Para leer un elemento se indica su fila y columna:
- Lee el valor en fila 1, columna 2.
Modificar un elemento usa la misma notación en el lado izquierdo de una asignación:
- Reemplaza el valor en fila 1, columna 2 con
99.
Recorrido de una matriz¶
Recorrido completo¶
Para visitar todos los elementos de una matriz se usan dos ciclos anidados: el exterior avanza por las filas y el interior recorre cada columna dentro de esa fila.
matriz = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
for i in range(len(matriz)): # (1)!
for j in range(len(matriz[i])): # (2)!
print(matriz[i][j], end=" ") # (3)!
print() # (4)!
- El ciclo exterior recorre el índice de fila
i, de0hasta el número de filas menos uno. - El ciclo interior recorre el índice de columna
j, de0hasta el número de columnas de esa fila menos uno. - Se imprime el elemento en la posición
[i][j]seguido de un espacio, sin saltar de línea. - Al terminar cada fila, se imprime un salto de línea para pasar a la siguiente.
Recorrido por filas¶
En ocasiones se necesita trabajar con una fila completa como si fuera una lista independiente. Python permite acceder directamente a ella con un solo índice:
- En cada iteración,
filaes la lista completa de esa fila:[1, 2, 3], luego[4, 5, 6], entre otros. - Se puede aplicar cualquier operación de lista, como
sum,max,minolen.
Recorrido por columnas¶
Recorrer columnas requiere fijar el índice de columna j y variar el de fila i:
for j in range(len(matriz[0])): # (1)!
for i in range(len(matriz)): # (2)!
print(matriz[i][j], end=" ")
print()
- El ciclo exterior fija la columna
j. - El ciclo interior recorre todas las filas para esa columna.
¿Cuándo recorrer por columnas?
El recorrido por columnas es útil cuando cada columna representa una categoría o materia y se quiere calcular un resultado por categoría: el promedio de cada materia, el máximo de cada columna, entre otros casos.
Operaciones elementales¶
Suma de todos los elementos¶
Para sumar todos los valores de una matriz se recorre elemento por elemento con el doble ciclo, acumulando en una variable:
matriz = [
[3, 7, 2],
[8, 1, 5],
[4, 6, 9],
]
total = 0
for fila in matriz: # (1)!
for elemento in fila: # (2)!
total += elemento
print(f"Suma total: {total}")
- Cada
filaes una lista; no se necesita el índice cuando solo se quiere el valor. - Cada
elementoes un número de esa fila.
Buscar el valor máximo y su posición¶
Encontrar el máximo requiere guardar no solo el valor, sino también su posición (i, j) dentro de la matriz:
max_val = matriz[0][0] # (1)!
max_i = 0
max_j = 0
for i in range(len(matriz)):
for j in range(len(matriz[i])):
if matriz[i][j] > max_val: # (2)!
max_val = matriz[i][j]
max_i = i
max_j = j
print(f"Máximo: {max_val} en posición ({max_i}, {max_j})")
- Se asume que el máximo inicial es el primer elemento. Así siempre hay un valor de comparación válido.
- Si se encuentra un valor mayor, se actualiza el máximo y se guarda su posición.
Promedio por fila¶
Calcular el promedio de cada fila de forma independiente es un caso muy frecuente: notas por estudiante, ventas por sucursal, temperaturas por día.
for i in range(len(matriz)):
promedio = sum(matriz[i]) / len(matriz[i]) # (1)!
print(f"Fila {i}: promedio = {promedio:.1f}")
sumylenreciben la fila completamatriz[i], que es una lista normal.
La matriz transpuesta¶
La transpuesta de una matriz es una nueva matriz donde las filas originales se convierten en columnas. El elemento que estaba en la posición [i][j] pasa a estar en la posición [j][i].
La matriz original tiene 2 filas y 3 columnas; su transpuesta tiene 3 filas y 2 columnas.
Para construirla se recorre la matriz original y se coloca cada elemento en su posición invertida:
original = [
[1, 2, 3],
[4, 5, 6],
]
filas = len(original) # (1)!
columnas = len(original[0]) # (2)!
transpuesta = []
for j in range(columnas): # (3)!
fila_nueva = []
for i in range(filas): # (4)!
fila_nueva.append(original[i][j])
transpuesta.append(fila_nueva)
for fila in transpuesta:
print(fila)
- La matriz original tiene 2 filas.
- La matriz original tiene 3 columnas.
- El ciclo exterior itera sobre las columnas de la original, que se convierten en las filas de la transpuesta.
- El ciclo interior toma el elemento
[i][j]de la original y lo coloca en la nueva fila.
Ejercicio integrador¶
Dos jugadores se turnan para jugar una partida de gato (tic-tac-toe). El programa debe gestionar el tablero, validar jugadas y determinar el resultado final.
El programa debe cumplir con los siguientes requisitos:
- Definir una función
crear_tablero()que retorne una matriz 3×3 inicializada con espacios vacíos. - Definir una función
mostrar_tablero(tablero)que imprima el tablero con separadores entre celdas y entre filas. - Definir una función
hacer_jugada(tablero, fila, columna, ficha)que coloque la ficha del jugador en la posición indicada. Si la celda ya está ocupada, debe retornarFalse; si la jugada es válida, debe realizarla y retornarTrue. - Definir una función
hay_ganador(tablero, ficha)que verifique si la ficha dada ocupa una fila completa, una columna completa o alguna de las dos diagonales. RetornaTruesi hay ganador,Falseen caso contrario. - Definir una función
tablero_lleno(tablero)que retorneTruesi no quedan celdas vacías. - El programa principal debe alternar los turnos entre el jugador
"X"y el jugador"O", pedir fila y columna en cada turno (valores entre 1 y 3), validar la jugada, mostrar el tablero actualizado y anunciar el resultado al terminar.
Paso 1: Crear y mostrar el tablero¶
El tablero es una matriz 3×3 de espacios. La función de visualización agrega separadores para que sea legible.
def crear_tablero():
tablero = []
for i in range(3): # (1)!
fila = []
for j in range(3):
fila.append(" ")
tablero.append(fila)
return tablero
def mostrar_tablero(tablero):
print()
for i in range(3):
print(f" {tablero[i][0]} | {tablero[i][1]} | {tablero[i][2]} ") # (2)!
if i < 2:
print("---+---+---")
print()
- Cada celda se inicializa con un espacio
" "para que el tablero se vea alineado al imprimirse. - Las celdas de cada fila se separan con
|; entre filas se imprime una línea de guiones.
Paso 2: Validar y registrar una jugada¶
def hacer_jugada(tablero, fila, columna, ficha):
if tablero[fila][columna] != " ": # (1)!
return False
tablero[fila][columna] = ficha # (2)!
return True
- Si la celda no está vacía, la jugada es inválida y se retorna
False. - Se coloca la ficha directamente en la posición de la matriz y se retorna
True.
Paso 3: Verificar si hay ganador¶
Se revisan las tres filas, las tres columnas y las dos diagonales:
def hay_ganador(tablero, ficha):
for i in range(3):
if all(tablero[i][j] == ficha for j in range(3)): # (1)!
return True
if all(tablero[j][i] == ficha for j in range(3)): # (2)!
return True
if all(tablero[i][i] == ficha for i in range(3)): # (3)!
return True
if all(tablero[i][2 - i] == ficha for i in range(3)): # (4)!
return True
return False
- Revisa la fila
i: las tres columnas deben tener la misma ficha. - Revisa la columna
i: las tres filas en esa columna deben tener la misma ficha. - Diagonal principal: posiciones
[0][0],[1][1],[2][2]. - Diagonal secundaria: posiciones
[0][2],[1][1],[2][0].
Paso 4: Verificar tablero lleno¶
- Si alguna fila contiene al menos un espacio vacío, el tablero no está lleno.
Paso 5: Programa principal¶
El ciclo principal alterna turnos, pide la jugada, la valida y verifica el estado del juego tras cada movimiento:
def jugar():
tablero = crear_tablero()
turno = 0
fichas = ["X", "O"]
while True:
ficha = fichas[turno % 2] # (1)!
mostrar_tablero(tablero)
print(f"Turno del jugador {ficha}")
while True: # (2)!
try:
fila = int(input("Fila (1-3): ")) - 1
columna = int(input("Columna (1-3): ")) - 1
if fila not in range(3) or columna not in range(3):
print("Posición fuera del tablero. Intente de nuevo.")
elif not hacer_jugada(tablero, fila, columna, ficha):
print("Esa celda ya está ocupada. Intente de nuevo.")
else:
break
except ValueError:
print("Ingrese un número entero.")
if hay_ganador(tablero, ficha): # (3)!
mostrar_tablero(tablero)
print(f"¡El jugador {ficha} gana!")
break
if tablero_lleno(tablero): # (4)!
mostrar_tablero(tablero)
print("¡Empate!")
break
turno += 1
- El módulo
% 2alterna entre0y1, seleccionando"X"o"O"en cada turno. - El ciclo interno repite el pedido de jugada hasta que el dato ingresado sea válido y la celda esté libre.
- Se verifica el ganador inmediatamente después de cada jugada válida.
- Si no hay ganador pero el tablero está lleno, el juego termina en empate.
Programa completo¶
def crear_tablero():
tablero = []
for i in range(3):
fila = []
for j in range(3):
fila.append(" ")
tablero.append(fila)
return tablero
def mostrar_tablero(tablero):
print()
for i in range(3):
print(f" {tablero[i][0]} | {tablero[i][1]} | {tablero[i][2]} ")
if i < 2:
print("---+---+---")
print()
def hacer_jugada(tablero, fila, columna, ficha):
if tablero[fila][columna] != " ":
return False
tablero[fila][columna] = ficha
return True
def hay_ganador(tablero, ficha):
for i in range(3):
if all(tablero[i][j] == ficha for j in range(3)):
return True
if all(tablero[j][i] == ficha for j in range(3)):
return True
if all(tablero[i][i] == ficha for i in range(3)):
return True
if all(tablero[i][2 - i] == ficha for i in range(3)):
return True
return False
def tablero_lleno(tablero):
for fila in tablero:
if " " in fila:
return False
return True
def jugar():
tablero = crear_tablero()
turno = 0
fichas = ["X", "O"]
while True:
ficha = fichas[turno % 2]
mostrar_tablero(tablero)
print(f"Turno del jugador {ficha}")
while True:
try:
fila = int(input("Fila (1-3): ")) - 1
columna = int(input("Columna (1-3): ")) - 1
if fila not in range(3) or columna not in range(3):
print("Posición fuera del tablero. Intente de nuevo.")
elif not hacer_jugada(tablero, fila, columna, ficha):
print("Esa celda ya está ocupada. Intente de nuevo.")
else:
break
except ValueError:
print("Ingrese un número entero.")
if hay_ganador(tablero, ficha):
mostrar_tablero(tablero)
print(f"¡El jugador {ficha} gana!")
break
if tablero_lleno(tablero):
mostrar_tablero(tablero)
print("¡Empate!")
break
turno += 1
jugar()