Las matrices son colecciones de números organizados en filas y columnas. Son fundamentales en álgebra lineal, gráficos y resolución de ecuaciones.
En este artículo, aprenderás cómo implementar una matriz en Python, incluyendo sus operaciones y características principales.
Puedes encontrar el código Python de este artículo en el repositorio de ejemplos de Python Scouts. ¡Te agradeceríamos mucho si nos dejas una estrella (⭐) en el repositorio!
¿Qué es una matriz?
Una matriz es una colección de números organizados en filas y columnas que forman una cuadrícula rectangular de tamaño fijo. Estas estructuras son muy útiles en diversas áreas, como el álgebra lineal y los gráficos por computadora.
También puedes usar matrices para representar y resolver sistemas de ecuaciones lineales, entre otras aplicaciones.
Las características fundamentales de una matriz incluyen las siguientes:
- Tamaño fijo: El número de filas y columnas se determina al crearla y no cambia.
- Acceso por índices: Los elementos se acceden mediante una pareja de índices enteros representando
(fila, columna). - Operaciones aritméticas: Las matrices soportan operaciones como suma, resta, multiplicación y escalado.
En Python, las matrices no están integradas como una estructura de datos. Aunque bibliotecas como NumPy proporcionan implementaciones optimizadas, en este artículo construirás tu propia matriz usando listas de listas y reforzarás conceptos de estructuras de datos en Python.
Operaciones comunes en una matriz
A continuación, un resumen de las operaciones que soportará tu matriz:
| Operación | Descripción |
|---|---|
matrix = Matrix(rows, cols) |
Construye una matriz de tamaño rows × cols con valores en 0. |
matrix = Matrix(rows, cols, default) |
Construye una matriz de tamaño rows × cols con valores en default. |
matrix.rows |
Devuelve el número de filas de la matriz. |
matrix.cols |
Devuelve el número de columnas de la matriz. |
matrix.size |
Devuelve el tamaño de la matriz como una tupla (filas, columnas). |
matrix.scale_by(scalar) |
Escala toda la matriz multiplicando por scalar. |
matrix.transpose() |
Devuelve una nueva matriz transpuesta. |
matrix.add(other) |
Devuelve una nueva matriz con la suma de matrix y other. |
matrix + other |
Devuelve una nueva matriz con la suma de matrix y other. |
matrix.subtract(other) |
Devuelve una nueva matriz con la resta de matrix y other. |
matrix - other |
Devuelve una nueva matriz con la resta de matrix y other. |
matrix.multiply(other) |
Devuelve una nueva matriz con el producto de matrix y other. |
matrix * other |
Devuelve una nueva matriz con el producto de matrix y other. |
matrix[i, j] |
Devuelve el valor en la celda (i, j). |
matrix[i, j] = value |
Asigna value a la celda (i, j). |
Matrix.from_list_of_lists(iterable) |
Construye una nueva matriz a partir de una lista de listas. |
Implementación de una matriz en Python
A continuación encontrarás la implementación de la matriz en Python:
from operator import add, sub
from typing import Any, Callable, NamedTuple, Self, Tuple
class Size(NamedTuple):
"""Represents a matrix size as a tuple (rows, cols)."""
rows: int
cols: int
class Matrix:
"""Represent a numeric matrix as an m x n rectangular grid."""
def __init__(self, rows: int, cols: int, default: int = 0) -> None:
self._rows = rows
self._cols = cols
self._data = [[default] * cols for _ in range(rows)]
@property
def rows(self) -> int:
"""Return the number of rows."""
return self._rows
@rows.setter
def rows(self, value: int) -> None:
"""Raise AttributeError when trying to assign rows."""
raise AttributeError("can't set 'rows'")
@property
def cols(self) -> int:
"""Return the number of columns."""
return self._cols
@cols.setter
def cols(self, value: int) -> None:
"""Raise AttributeError when trying to assign cols."""
raise AttributeError("can't set 'cols'")
@property
def size(self) -> Size:
"""Return the matrix size as a (rows, cols) tuple."""
return Size(self.rows, self.cols)
@size.setter
def size(self, value: Tuple[int, int]) -> None:
"""Raise AttributeError when trying to assign size."""
raise AttributeError("can't set 'size'")
def _validate_index(
self,
index: Tuple[int, int]
) -> Tuple[int, int]:
"""Validate an index and return it as a (row, col) tuple."""
if not isinstance(index, tuple) or len(index) != 2:
raise IndexError("index must be a tuple of two integers")
row, col = index
if not (0 <= row < self.rows):
raise IndexError(
f"row index out of range: {row}. "
f"Valid range: 0 to {self.rows - 1}"
)
if not (0 <= col < self.cols):
raise IndexError(
f"column index out of range: {col}. "
f"Valid range: 0 to {self.cols - 1}"
)
return row, col
def scale_by(self, scalar: int) -> None:
"""Scale the matrix by a scalar."""
for i, row in enumerate(self._data):
for j, _ in enumerate(row):
self[i, j] *= scalar
def transpose(self) -> Self:
"""Return the transpose of the current matrix."""
transposed = type(self)(self.cols, self.rows)
transposed._data = [list(row) for row in zip(*self._data)]
return transposed
def add(self, other: Self) -> Self:
"""Return the sum of this matrix and another matrix."""
return self.__add__(other)
def __add__(self, other: Self) -> Self:
return self._compute(other, operation=add)
def _compute(self, other: Self, operation: Callable) -> Self:
if other.__class__ is not self.__class__:
raise TypeError("expected a Matrix object")
if other.size != self.size:
raise ValueError("invalid matrix size")
matrix = type(self)(self._rows, self._cols)
for i, row in enumerate(self._data):
for j, _ in enumerate(row):
matrix[i, j] = operation(self[i, j], other[i, j])
return matrix
def subtract(self, other: Self) -> Self:
"""Return the difference between this matrix and another."""
return self.__sub__(other)
def __sub__(self, other: Self) -> Self:
return self._compute(other, operation=sub)
def multiply(self, other: Self) -> Self:
"""Return the product of this matrix and another matrix."""
return self.__mul__(other)
def __mul__(self, other: Self) -> Self:
if other.__class__ is not self.__class__:
raise TypeError("expected a Matrix object")
if self.cols != other.rows:
raise ValueError("invalid matrix size")
matrix = type(self)(self.rows, other.cols)
for i in range(self.rows):
for j in range(other.cols):
for k in range(other.rows):
matrix[i, j] += self[i, k] * other[k, j]
return matrix
@classmethod
def from_list_of_lists(cls, iterable: list[list[Any]], /) -> Self:
"""Return a new matrix built from a list of lists."""
if len(set(len(row) for row in iterable)) > 1:
raise ValueError("invalid matrix size")
matrix = cls(rows=len(iterable), cols=len(iterable[0]))
for i, rows in enumerate(iterable):
for j, value in enumerate(rows):
matrix[i, j] = value
return matrix
def __getitem__(self, index: Tuple[int, int]) -> Any:
row, col = self._validate_index(index)
return self._data[row][col]
def __setitem__(self, index: Tuple[int, int], value: Any) -> None:
row, col = self._validate_index(index)
self._data[row][col] = value
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}"
f"(rows={self._rows}, cols={self._cols})"
)
def __str__(self) -> str:
return (
f"{self.__class__.__name__}"
f"({' '.join(str(row) for row in self._data)})"
)
El atributo ._data es una lista de listas que almacena los valores de la matriz. Cada sublista representa una fila de la matriz. Los atributos ._rows y ._cols almacenan las dimensiones, y se exponen como propiedades de solo lectura.
El método auxiliar privado ._validate_index() verifica que el índice sea una tupla de dos enteros dentro del rango válido de la matriz. Esto reemplaza la dependencia externa pyadt.utils.validate_index del borrador original.
En las anotaciones de tipo, la clase usa Self para indicar que los métodos que combinan, transforman o construyen matrices devuelven instancias del mismo tipo de la clase actual. Esto mejora la compatibilidad con subclases y hace el tipado más preciso.
Los métodos principales te permiten ofrecer las siguientes funcionalidades:
.scale_by(): multiplica todos los elementos de la matriz por un escalar..transpose(): devuelve una nueva matriz transpuesta intercambiando filas y columnas..add(): devuelve una nueva matriz con la suma de dos matrices del mismo tamaño..subtract(): devuelve una nueva matriz con la resta de dos matrices del mismo tamaño..multiply(): devuelve una nueva matriz con el producto de dos matrices compatibles..from_list_of_lists(): método de clase que construye una matriz a partir de una lista de listas y retorna una instancia del tipo actual.
Los métodos especiales te permiten ofrecer las siguientes funcionalidades:
.__getitem__()y.__setitem__(): permiten acceder y asignar valores con la sintaxismatrix[i, j]..__add__(),.__sub__()y.__mul__(): permiten usar los operadores+,-y*..__repr__()y.__str__(): devuelven representaciones de cadena legibles.
Ejemplo de uso de la matriz
En esta sección verás ejemplos prácticos de cómo crear y manipular instancias de tu clase Matrix.
Inicialización
Para crear una Matrix, necesitas indicar el número de filas y columnas. Opcionalmente, puedes especificar un valor por defecto:
>>> matrix = Matrix(3, 3)
>>> print(matrix)
Matrix([0, 0, 0] [0, 0, 0] [0, 0, 0])
>>> matrix = Matrix(2, 3, 1)
>>> print(matrix)
Matrix([1, 1, 1] [1, 1, 1])
También puedes construir una matriz a partir de una lista de listas:
>>> matrix = Matrix.from_list_of_lists([[1, 2], [3, 4], [5, 6]])
>>> print(matrix)
Matrix([1, 2] [3, 4] [5, 6])
Acceso y modificación de elementos
Puedes leer y escribir elementos con la sintaxis matrix[fila, columna]:
>>> matrix = Matrix(2, 2)
>>> matrix[0, 0] = 10
>>> matrix[0, 1] = 20
>>> matrix[1, 0] = 30
>>> matrix[1, 1] = 40
>>> print(matrix)
Matrix([10, 20] [30, 40])
>>> matrix[0, 0]
10
>>> matrix[1, 1]
40
Propiedades de la matriz
Las propiedades .rows, .cols y .size devuelven las dimensiones de la matriz y son de solo lectura:
>>> matrix = Matrix(4, 3)
>>> matrix.rows
4
>>> matrix.cols
3
>>> matrix.size
Size(rows=4, cols=3)
Escalado
El método .scale_by() multiplica todos los elementos por un escalar:
>>> matrix = Matrix(2, 2, 1)
>>> print(matrix)
Matrix([1, 1] [1, 1])
>>> matrix.scale_by(5)
>>> print(matrix)
Matrix([5, 5] [5, 5])
Transposición
El método .transpose() devuelve una nueva matriz donde las filas se convierten en columnas:
>>> matrix = Matrix.from_list_of_lists([[1, 2, 3], [4, 5, 6]])
>>> print(matrix)
Matrix([1, 2, 3] [4, 5, 6])
>>> transposed = matrix.transpose()
>>> print(transposed)
Matrix([1, 4] [2, 5] [3, 6])
Operaciones aritméticas
Puedes sumar, restar y multiplicar matrices usando métodos o los operadores en Python +, - y *:
>>> matrix_a = Matrix(2, 2, 10)
>>> matrix_b = Matrix(2, 2, 3)
>>> result = matrix_a + matrix_b
>>> print(result)
Matrix([13, 13] [13, 13])
>>> result = matrix_a - matrix_b
>>> print(result)
Matrix([7, 7] [7, 7])
Para la multiplicación, las dimensiones deben ser compatibles:
>>> matrix_a = Matrix.from_list_of_lists([[1, 2], [3, 4]])
>>> matrix_b = Matrix.from_list_of_lists([[5, 6], [7, 8]])
>>> result = matrix_a * matrix_b
>>> print(result)
Matrix([19, 22] [43, 50])
Representación en cadena
Los métodos .__str__() y .__repr__() proporcionan representaciones legibles:
>>> matrix = Matrix(2, 3, 1)
>>> print(matrix)
Matrix([1, 1, 1] [1, 1, 1])
>>> print(repr(matrix))
Matrix(rows=2, cols=3)
Conclusión
Has aprendido cómo implementar una matriz que proporciona operaciones esenciales como acceso por índices, escalado, transposición, suma, resta y multiplicación.
Este conocimiento te sirve de base para comprender cómo se representan datos bidimensionales y para abordar problemas de álgebra lineal y gráficos por computadora.
Puedes encontrar el código Python de este artículo en el repositorio de ejemplos de Python Scouts. ¡Te agradeceríamos mucho si nos dejas una estrella (⭐) en el repositorio!