Files
2026-10-22 00:45:17 +02:00

172 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Utility functions for matrix operations.
This module contains:
* ``matmul`` – standard matrix multiplication.
* ``transpose`` – row↔column swapping.
* ``rotate`` – 90‑degree clockwise rotations.
All functions use type hints and raise informative errors where
dimensions are incompatible or matrices are non‑rectangular.
"""
from __future__ import annotations
from tabulate import tabulate
Matrix = list[list[int | float]]
def matmul(a: Matrix, b: Matrix) -> Matrix:
"""Return the matrix product a @ b.
Parameters
----------
a : Matrix
Left‑hand matrix.
b : Matrix
Right‑hand matrix.
Returns
-------
Matrix
Product matrix.
Raises
------
ValueError
If matrices are not rectangular or dimensions are incompatible.
"""
def _rect(mat: Matrix) -> bool:
return all(len(row) == len(mat[0]) for row in mat)
if not a or not b:
raise ValueError("matrices may not be empty")
if not _rect(a) or not _rect(b):
raise ValueError("input matrices must be rectangular")
r1, c1 = len(a), len(a[0])
r2, c2 = len(b), len(b[0])
if c1 != r2:
raise ValueError("first matrix columns must equal second matrix rows")
result: Matrix = [[0 for _ in range(c2)] for _ in range(r1)]
for i in range(r1):
for j in range(c2):
s = 0
for k in range(c1):
s += a[i][k] * b[k][j]
result[i][j] = s
return result
def transpose(matrix: Matrix) -> Matrix:
"""Return the transpose of a 2‑D matrix.
The transpose operation swaps rows and columns: each row in the
result is a column from the input matrix.
Parameters
----------
matrix : Matrix
Two‑dimensional list of numbers. All rows must contain the
same number of columns.
Returns
-------
Matrix
Transposed matrix.
Raises
------
ValueError
If *matrix* is empty or if the rows have differing lengths.
"""
if not matrix:
raise ValueError("transpose requires a non‑empty matrix")
if any(len(row) != len(matrix[0]) for row in matrix):
raise ValueError("matrix must be rectangular to transpose")
rows, cols = len(matrix), len(matrix[0])
return [[matrix[i][j] for i in range(rows)] for j in range(cols)]
def rot_2D(theta: float) -> list[list[float]]:
"""Return the 2‑D rotation matrix for angle *theta* in degrees.
The matrix is
``[[cos(θ), -sin(θ)],
[sin(θ), cos(θ)]]``
where θ is interpreted as a clockwise rotation.
Parameters
----------
theta : float
Rotation angle in degrees (clockwise).
Returns
-------
list[list[float]]
2x2 rotation matrix.
"""
import math
rad = math.radians(theta)
cos_t = math.cos(rad)
sin_t = math.sin(rad)
return [[cos_t, -sin_t], [sin_t, cos_t]]
def rotate(matrix: Matrix, degrees: float) -> Matrix:
"""Rotate the matrix clockwise by 0, 90, 180, or 270 degrees.
Parameters
----------
matrix : Matrix
degrees : float
Must be one of 0, 90, 180, 270.
Returns
-------
Matrix
Raises
------
ValueError
If *degrees* is unsupported.
"""
if degrees not in (0, 90, 180, 270):
raise ValueError("degrees must be one of 0, 90, 180, 270")
if not matrix:
return []
n_rows, n_cols = len(matrix), len(matrix[0])
if degrees == 0:
return [row[:] for row in matrix]
if degrees == 90:
return [[matrix[n_rows - 1 - j][i] for j in range(n_rows)] for i in range(n_cols)]
if degrees == 180:
return [[matrix[n_rows - 1 - i][n_cols - 1 - j] for j in range(n_cols)] for i in range(n_rows)]
# 270
return [[matrix[j][n_cols - 1 - i] for j in range(n_rows)] for i in range(n_cols)]
if __name__ == "__main__":
# matmul tests
a1 = [[1, 2], [3, 4]]
b1 = [[5, 6], [7, 8]]
assert matmul(a1, b1) == [[19, 22], [43, 50]], "matmul example 1 failed"
a2 = [[1], [2], [3]]
b2 = [[4, 5, 6]]
assert matmul(a2, b2) == [[4, 5, 6], [8, 10, 12], [12, 15, 18]], "matmul example 2 failed"
# transpose test
mat = [[1, 2, 3], [4, 5, 6]]
assert transpose(mat) == [[1, 4], [2, 5], [3, 6]], "transpose failed"
# rotation tests
mat3 = [[1, 2], [3, 4], [5, 6]]
assert rotate(mat3, 90) == [[5, 3, 1], [6, 4, 2]], "rotate 90 failed"
assert rotate(mat3, 180) == [[6, 5], [4, 3], [2, 1]], "rotate 180 failed"
assert rotate(mat3, 270) == [[2, 4, 6], [1, 3, 5]], "rotate 270 failed"
assert rotate(mat3, 0) == mat3, "rotate 0 failed"
print(tabulate(rot_2D(1.0)))