Erster Stand

This commit is contained in:
erik detzel committed 2026-10-15 15:23:25 +02:00
commit 397ab45018
10 files changed
+315

No files matched your search

+260
View File
@@ -0,0 +1,260 @@
"""Provides matmul, transpose, and rotate functions for 2‑D numeric matrices."""
from __future__ import annotations
import math
from collections.abc import Iterable
from typing import Literal
__all__ = ["matmul", "rot_2d", "rotate", "transpose"]
_NUMERIC_TYPES = (int, float)
def _validate_matrix(
matrix: Iterable[Iterable[object]], name: str
) -> list[list[float]]:
"""Validate that *matrix* is a rectangular list of lists of numeric values.
Returns the matrix converted to ``list[list[float]]`` if valid.
Raises ``TypeError`` for non‑numeric values or if any element is a ``bool``.
Raises ``ValueError`` for empty matrices, empty rows, or ragged matrices.
"""
try:
rows = list(map(list, matrix))
except TypeError as exc:
raise TypeError(f"{name} must be a sequence of sequences") from exc
if not rows:
raise ValueError(f"{name} must not be empty")
if not rows[0]:
raise ValueError(f"{name} must not contain empty rows")
row_len = len(rows[0])
for row in rows:
if len(row) != row_len:
raise ValueError(f"{name} must be rectangular (row lengths differ)")
for value in row:
if isinstance(value, bool):
raise TypeError(f"{name} contains boolean value {value!r}")
if not isinstance(value, _NUMERIC_TYPES):
raise TypeError(f"{name} contains non‑numeric value {value!r}")
return [[float(value) for value in row] for row in rows]
def matmul(a: list[list[float]], b: list[list[float]]) -> list[list[float]]:
"""Multiply two matrices ``a`` and ``b``.
Parameters
----------
a: list[list[float]]
Left operand matrix.
b: list[list[float]]
Right operand matrix.
Returns
-------
list[list[float]]
The product matrix.
Raises
------
ValueError
If matrix dimensions are incompatible or if a matrix is ragged.
TypeError
If inputs are not sequences of numeric values.
"""
a_matrix = _validate_matrix(a, "a")
b_matrix = _validate_matrix(b, "b")
num_rows_a = len(a_matrix)
num_cols_a = len(a_matrix[0])
num_rows_b = len(b_matrix)
num_cols_b = len(b_matrix[0])
if num_cols_a != num_rows_b:
raise ValueError(
f"Incompatible dimensions: a has {num_cols_a} columns; "
f"b has {num_rows_b} rows"
)
result: list[list[float]] = [
[0.0 for _ in range(num_cols_b)] for _ in range(num_rows_a)
]
for i in range(num_rows_a):
for j in range(num_cols_b):
dot_product = 0.0
for k in range(num_cols_a):
dot_product += a_matrix[i][k] * b_matrix[k][j]
result[i][j] = dot_product
return result
def transpose(matrix: list[list[float]]) -> list[list[float]]:
"""Return the transpose of *matrix*.
Parameters
----------
matrix: list[list[float]]
Input matrix as a list of rows.
Returns
-------
list[list[float]]
Transposed matrix.
Raises
------
TypeError, ValueError
Propagated from :func:`_validate_matrix` when the input is not a
rectangular numeric matrix.
"""
m_matrix = _validate_matrix(matrix, "matrix")
rows, cols = len(m_matrix), len(m_matrix[0])
return [[m_matrix[i][j] for i in range(rows)] for j in range(cols)]
def rotate(matrix: list[list[float]]) -> list[list[float]]:
"""Return the matrix rotated 90° clockwise.
Parameters
----------
matrix: list[list[float]]
Input matrix as a list of rows.
Returns
-------
list[list[float]]
Rotated matrix.
Raises
------
TypeError, ValueError
Propagated from :func:`_validate_matrix` when the input is not a
rectangular numeric matrix.
"""
m_matrix = _validate_matrix(matrix, "matrix")
return [list(row) for row in zip(*reversed(m_matrix))]
def rot_2d(angle: float, unit: Literal["deg", "rad"] = "deg") -> list[list[float]]:
"""Return a 2×2 rotation matrix for a given angle.
Parameters
----------
angle : float
Rotation angle.
unit : {"deg", "rad"}, default "deg"
Whether *angle* is given in degrees or radians.
Returns
-------
list[list[float]]
2×2 rotation matrix.
"""
theta = math.radians(angle) if unit == "deg" else angle
c = math.cos(theta)
s = math.sin(theta)
return [[c, -s], [s, c]]
def print_matrix(title: str, matrix: list[list[float]]) -> None:
"""Print a title followed by each matrix row on its own line."""
print(title)
for row in matrix:
print(" ", row)
if __name__ == "__main__":
# Matrix multiplication tests
a1 = [[1, 2, 3], [4, 5, 6]]
b1 = [[7, 8], [9, 10], [11, 12]]
print_matrix("Matrix A:", a1)
print_matrix("Matrix B:", b1)
product = matmul(a1, b1)
print_matrix("A x B:", product)
assert product == [[58, 64], [139, 154]], "matmul first test failed"
print()
a2 = [[1, 2], [3, 4]]
b2 = [[5, 6], [7, 8]]
print_matrix("Matrix A:", a2)
print_matrix("Matrix B:", b2)
product = matmul(a2, b2)
print_matrix("A x B:", product)
assert product == [[19, 22], [43, 50]], "matmul second test failed"
print()
identity = [[1, 0], [0, 1]]
print_matrix("Matrix A:", a2)
print_matrix("Identity matrix:", identity)
product = matmul(a2, identity)
print_matrix("A x Identity:", product)
assert product == a2, "matmul identity test failed"
print()
# Dimension mismatch must raise ValueError
try:
matmul([[1, 2]], [[1, 2]])
except ValueError as error:
print(f"Expected error for mismatched dimensions: {error}")
else:
raise AssertionError("matmul did not raise ValueError")
print()
# Transpose tests
print_matrix("Matrix:", a1)
transposed = transpose(a1)
print_matrix("Transposed:", transposed)
assert transposed == [[1, 4], [2, 5], [3, 6]], "transpose first test failed"
assert transpose(transposed) == a1, "double transpose failed"
print()
column = [[1], [2], [3]]
print_matrix("Matrix:", column)
transposed = transpose(column)
print_matrix("Transposed:", transposed)
assert transposed == [[1, 2, 3]], "transpose column test failed"
print()
# Rotation tests
print_matrix("Matrix:", a1)
rotated = rotate(a1)
print_matrix("Rotated 90 degrees clockwise:", rotated)
assert rotated == [[4, 1], [5, 2], [6, 3]], "rotate 2x3 test failed"
print()
square = [[1, 2], [3, 4]]
print_matrix("Matrix:", square)
rotated = rotate(square)
print_matrix("Rotated 90 degrees clockwise:", rotated)
assert rotated == [[3, 1], [4, 2]], "rotate square test failed"
print()
# Four rotations must return the original matrix
rotated = square
for _ in range(4):
rotated = rotate(rotated)
print_matrix("Square matrix rotated four times:", rotated)
assert rotated == square, "four rotations did not return original"
print()
# 2D rotation matrix tests
rotation_0 = rot_2d(0)
print_matrix("Rotation matrix for 0 degrees:", rotation_0)
assert rotation_0 == [[1.0, -0.0], [0.0, 1.0]], "rot_2d 0 degrees failed"
print()
rotation_90 = rot_2d(90)
print_matrix("Rotation matrix for 90 degrees:", rotation_90)
point = [[1], [0]]
rotated_point = matmul(rotation_90, point)
print_matrix("Point (1, 0) rotated by 90 degrees:", rotated_point)
assert math.isclose(rotated_point[0][0], 0.0, abs_tol=1e-9), "rot_2d x failed"
assert math.isclose(rotated_point[1][0], 1.0, abs_tol=1e-9), "rot_2d y failed"
print()
print("All tests passed.")