102 lines
2.7 KiB
Python
102 lines
2.7 KiB
Python
"""Matrix multiplication with explicit dimension checks."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
|
|
Matrix = Sequence[Sequence[float]]
|
|
|
|
|
|
def matmul(left: Matrix, right: Matrix) -> list[list[float]]:
|
|
"""Multiply two matrices and return a new matrix.
|
|
|
|
Args:
|
|
left: Matrix of shape (rows_left, cols_left).
|
|
right: Matrix of shape (rows_right, cols_right).
|
|
|
|
Returns:
|
|
A new matrix of shape (rows_left, cols_right).
|
|
|
|
Raises:
|
|
ValueError: If either input is empty, ragged, or if
|
|
cols_left != rows_right.
|
|
"""
|
|
_validate(left, "left")
|
|
_validate(right, "right")
|
|
|
|
cols_left = len(left[0])
|
|
rows_right = len(right)
|
|
if cols_left != rows_right:
|
|
raise ValueError(
|
|
f"dimension mismatch: left has {cols_left} columns, "
|
|
f"right has {rows_right} rows"
|
|
)
|
|
|
|
cols_right = len(right[0])
|
|
result: list[list[float]] = []
|
|
for i in range(len(left)):
|
|
row: list[float] = []
|
|
for j in range(cols_right):
|
|
total = 0.0
|
|
for k in range(cols_left):
|
|
total += left[i][k] * right[k][j]
|
|
row.append(total)
|
|
result.append(row)
|
|
return result
|
|
|
|
|
|
def _validate(matrix: Matrix, name: str) -> None:
|
|
"""Check that *matrix* is a non-empty, rectangular 2D sequence."""
|
|
if len(matrix) == 0:
|
|
raise ValueError(f"{name} matrix is empty")
|
|
width = len(matrix[0])
|
|
if width == 0:
|
|
raise ValueError(f"{name} matrix has no columns")
|
|
for index, row in enumerate(matrix):
|
|
if len(row) != width:
|
|
raise ValueError(
|
|
f"{name} matrix is ragged: row 0 has {width} columns, "
|
|
f"row {index} has {len(row)}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Example 1: standard 2x3 * 3x2 multiplication.
|
|
a = [
|
|
[1, 2, 3],
|
|
[4, 5, 6],
|
|
]
|
|
b = [
|
|
[7, 8],
|
|
[9, 10],
|
|
[11, 12],
|
|
]
|
|
expected = [
|
|
[58, 64],
|
|
[139, 154],
|
|
]
|
|
assert matmul(a, b) == expected, f"example 1 failed: {matmul(a, b)}"
|
|
print("example 1 passed:", matmul(a, b))
|
|
|
|
# Example 2: multiplication with the identity matrix changes nothing.
|
|
identity = [
|
|
[1, 0, 0],
|
|
[0, 1, 0],
|
|
[0, 0, 1],
|
|
]
|
|
c = [
|
|
[2, -1, 0],
|
|
[0, 3, 4],
|
|
[5, 6, -2],
|
|
]
|
|
assert matmul(c, identity) == c, "example 2 failed"
|
|
print("example 2 passed:", matmul(c, identity))
|
|
|
|
# Example 3: mismatched dimensions must raise ValueError.
|
|
try:
|
|
matmul([[1, 2, 3]], [[1, 2]])
|
|
except ValueError as error:
|
|
print("example 3 passed:", error)
|
|
else:
|
|
raise AssertionError("example 3 failed: no ValueError raised")
|