
摘要
本文基于你给出的一段矩阵类(Matrix)片段,整理并补全为一个可运行、易理解、有实际场景的实现。文章采用接近日常交流的口吻,会讲清楚为什么要这样实现、每个方法的代码解析、以及用在真实场景中的例子(例如图像处理、数据表运算、线性代数小工具等)。最后给出示例测试、运行结果、时间复杂度与空间复杂度分析,并做总结。
描述
你给出的代码片段里包含对矩阵按位置赋值(支持索引与二维索引)、矩阵相等判断、矩阵相加和相减的若干方法,但格式上有错乱和不完整。我会把这些功能整理成一个完整的 Matrix 类,保持接口直观(支持 m[i]、m[i,j] 赋值与读取;支持 ==、+、-),并把内部实现写清楚、注释说明每一步的理由和潜在坑(比如深拷贝、维度检测、错误处理等)。
我们会用一个常见的实际场景来驱动这段代码:假设你在做一款小工具,用于处理表格数据或灰度图像(图像可以看成矩阵)------例如对两张同尺寸图像做差分检测、对两个实验数据表做元素级相加以得到合并结果,或者比较两个矩阵是否一致以校验数据传输。文章会给出示例代码和运行结果。
题解答案
下面是我整理并实现的 Matrix 类,包含:
- 构造(
__init__) - 可读写索引(
__getitem__,__setitem__,支持单索引和双索引) - 相等比较(
__eq__) - 加法(
__add__) - 减法(
__sub__) - 简单的字符串表示(
__str__)
完整代码如下(可直接复制运行):
python
import copy
class Matrix:
"""
简单的矩阵类,支持按元素访问、赋值,支持 ==、+、- 操作。
行、列索引在接口上使用 1-based(与题目代码一致),内部使用 0-based 存储。
"""
def __init__(self, rows: int, cols: int, fill: float = 0.0):
if rows <= 0 or cols <= 0:
raise ValueError("rows and cols must be positive integers")
self.row = rows
self.column = cols
# 内部存储为 list of lists,0-based
self.matrix = [[copy.deepcopy(fill) for _ in range(cols)] for _ in range(rows)]
@property
def shape(self):
return (self.row, self.column)
def __getitem__(self, index):
"""
支持两种索引方式:
m[i] -> 返回第 i 行(1-based),作为列表(浅拷贝)
m[i, j] -> 返回第 i 行第 j 列元素(1-based)
"""
if isinstance(index, int):
i = index
if i < 1 or i > self.row:
raise IndexError("row index out of range (1-based)")
# 返回一份浅拷贝,避免外部直接修改内部结构
return self.matrix[i-1].copy()
elif isinstance(index, tuple) and len(index) == 2:
i, j = index
if i < 1 or i > self.row or j < 1 or j > self.column:
raise IndexError("index out of range (1-based)")
return self.matrix[i-1][j-1]
else:
raise TypeError("index must be int or tuple of two ints")
def __setitem__(self, index, value):
"""
支持:
m[i] = a_list -> 将第 i 行全部替换(会进行深拷贝)
m[i,j] = value -> 将单个元素赋值
"""
if isinstance(index, int):
i = index
if not isinstance(value, (list, tuple)):
raise TypeError("assigning to a row requires a list/tuple")
if len(value) != self.column:
raise ValueError("assigned row length does not match number of columns")
if i < 1 or i > self.row:
raise IndexError("row index out of range (1-based)")
# 深拷贝一份,避免外部引用影响内部
self.matrix[i-1] = copy.deepcopy(list(value))
elif isinstance(index, tuple) and len(index) == 2:
i, j = index
if i < 1 or i > self.row or j < 1 or j > self.column:
raise IndexError("index out of range (1-based)")
self.matrix[i-1][j-1] = value
else:
raise TypeError("index must be int or tuple of two ints")
def __eq__(self, B):
"""
判断两个矩阵是否相等(元素逐一比较)。
需要 B 是 Matrix 类型或具有 shape 和 matrix 属性的对象。
"""
if not isinstance(B, Matrix):
return False
if self.shape != B.shape:
return False
for i in range(self.row):
for j in range(self.column):
if self.matrix[i][j] != B.matrix[i][j]:
return False
return True
def __add__(self, B):
"""
矩阵逐元素相加,返回新的 Matrix 对象。如果维度不同,抛出 ValueError。
"""
if not isinstance(B, Matrix):
raise TypeError("Can only add Matrix with Matrix")
if self.shape != B.shape:
raise ValueError("shape mismatch: cannot add matrices with different shape")
M = Matrix(self.row, self.column)
for i in range(self.row):
for j in range(self.column):
M.matrix[i][j] = self.matrix[i][j] + B.matrix[i][j]
return M
def __sub__(self, B):
"""
矩阵逐元素相减,返回新的 Matrix 对象。如果维度不同,抛出 ValueError。
"""
if not isinstance(B, Matrix):
raise TypeError("Can only subtract Matrix with Matrix")
if self.shape != B.shape:
raise ValueError("shape mismatch: cannot subtract matrices with different shape")
M = Matrix(self.row, self.column)
for i in range(self.row):
for j in range(self.column):
M.matrix[i][j] = self.matrix[i][j] - B.matrix[i][j]
return M
def __str__(self):
lines = []
for row in self.matrix:
lines.append("[" + ", ".join(str(x) for x in row) + "]")
return "\n".join(lines)
# 为了方便调试,提供一个复制方法
def copy(self):
M = Matrix(self.row, self.column)
M.matrix = copy.deepcopy(self.matrix)
return M
题解代码分析
接下来逐一解释上面代码的关键点和设计考虑。
-
构造
__init__:- 要求 rows 和 cols 为正整数,避免不合理构造。
- 内部用
self.matrix存列表的列表。使用copy.deepcopy(fill)保证如果fill是可变对象也被安全复制(虽然通常是数值)。 - 设计为 1-based 索引对外接口(和题目保持一致),但内部仍用 0-based 列表索引。
-
shape属性:- 返回
(rows, cols),便于外部检查维度相等。
- 返回
-
__getitem__:- 支持
m[i](返回第 i 行)与m[i, j](返回第 i 行第 j 列)这两种常见访问方式。 m[i]返回行时用.copy(),避免用户拿到引用后直接改动self.matrix的内部数据(如果需要改动行,建议使用m[i] = ...显式赋值)。- 对索引超界进行检查并抛出
IndexError。
- 支持
-
__setitem__:- 两种赋值方式都支持:按行赋值和按元素赋值。
- 按行赋值时,要求提供的
value是 list 或 tuple,并且长度须匹配列数,并深拷贝以防止后续外部修改影响内部矩阵。 - 按元素赋值时,直接替换对应位置的值。
- 设计上明确区分了错误类型(
TypeError、ValueError、IndexError),便于上层调用者捕获与调试。
-
__eq__:- 首先检查对象类型是否为
Matrix(或你可以放宽要求,但这里做严格检查),然后检查shape,最后逐元素比较。只要有一个元素不同则返回False,否则True。
- 首先检查对象类型是否为
-
__add__、__sub__:- 这里都要求另一个操作数是
Matrix,并且维度相同。返回新的Matrix对象而不是修改原矩阵(函数式风格,更安全)。 - 在内部通过循环逐元素赋值。如果遇到维度不同,抛出
ValueError,比返回字符串更 Pythonic、更利于异常处理与调试。
- 这里都要求另一个操作数是
-
__str__:- 提供一个简单的行格式打印,便于调试与展示。
-
copy:- 提供深拷贝的方法,方便需要保存快照的场景(例如在算法中保存中间状态)。
示例测试及结果
下面给出几个实际示例,演示如何在常见场景中使用该类,并展示运行结果。
示例 A:基础操作(构造、赋值、读取、打印)
python
# 构造 3x3 矩阵
A = Matrix(3, 3, fill=0)
# 按元素赋值(1-based)
A[1,1] = 10
A[1,2] = 20
A[3,3] = 30
# 按行赋值
A[2] = [1, 2, 3]
print("A =")
print(A)
运行结果(打印):
A =
[10, 20, 0]
[1, 2, 3]
[0, 0, 30]
示例 B:矩阵相加与相减(模拟两张同尺寸灰度图像的像素相减)
python
M1 = Matrix(2, 2)
M1[1] = [100, 120]
M1[2] = [90, 110]
M2 = Matrix(2, 2)
M2[1] = [95, 125]
M2[2] = [88, 115]
sum_mat = M1 + M2
diff_mat = M1 - M2
print("M1 + M2 =")
print(sum_mat)
print("M1 - M2 =")
print(diff_mat)
运行结果:
M1 + M2 =
[195, 245]
[178, 225]
M1 - M2 =
[5, -5]
[2, -5]
这里 "差分" M1 - M2 能直接反映像素亮度的变化(比如在图像差分检测中可用于运动检测或变更检测)。
示例 C:相等比较(校验数据一致性)
python
A = Matrix(2,2)
A[1] = [1,2]
A[2] = [3,4]
B = A.copy()
print("A == B ?", A == B) # True
B[2,2] = 999
print("修改后 A == B ?", A == B) # False
运行结果:
A == B ? True
修改后 A == B ? False
示例 D:错误处理示例(不同维度相加)
python
X = Matrix(2,3)
Y = Matrix(3,2)
try:
Z = X + Y
except Exception as e:
print("错误:", e)
输出:
错误: shape mismatch: cannot add matrices with different shape
时间复杂度
对于我们实现的主要操作,复杂度如下(设矩阵为 m x n):
- 构造(
__init__):需要创建m*n个元素 -> 时间复杂度 (O(mn))。 - 读取单元素(
__getitem__的(i,j)):常数时间 -> (O(1))。 - 读取整行(
__getitem__的i):行复制 -> (O(n))(复制一行)。 - 行赋值(
__setitem__的i = list):需要复制一行 -> (O(n))。 - 单元素赋值:常数时间 (O(1))。
- 相等比较(
__eq__):需要逐元素比较 -> 最坏 (O(mn))。 - 相加 / 相减(
__add__/__sub__):逐元素处理并生成新矩阵 -> (O(mn))。
空间复杂度
- 单个
Matrix对象占用空间与元素数量成正比 -> (O(mn))。 - 相加/相减会创建一个新的矩阵,临时在内存中同时存在两个(或更多)矩阵,因此在执行
C = A + B时额外需要 (O(mn)) 的空间(结果矩阵),加上输入矩阵的空间总共 (O(mn))(常量因子不同,但渐进相同)。 - 行赋值或复制会产生额外的行长度拷贝(长度为 n),所以局部峰值也可能略高于 (O(mn)),但总体仍是 (O(mn))。
与实际场景结合(举几个贴近日常的应用场景)
下面给出三个典型、容易理解的现实例子,说明这个 Matrix 类如何被用到实际项目中。
图像差分(变更检测)
- 场景描述:你在做一个简易监控系统,摄像头每隔一段时间截取灰度图(转换成二维矩阵)。你想检测画面是否有明显变化(有人走进镜头),可以对连续两帧做像素级差分(逐元素相减),然后统计差分矩阵中绝对值超过阈值的像素数。
- 为什么用这里的实现:本类的逐元素相减直接给出差分矩阵,易于后续统计与阈值处理。
表格数据合并(实验数据求和)
- 场景描述:两位同学进行了同样的实验并录入了二维结果(每行是实验样本,每列是不同指标)。现在需要把两组结果逐元素相加得到总和(例如合并实验次数或累计测量值)。
- 为什么用这里的实现:矩阵相加语义明确,并在维度不匹配时抛出异常,提醒数据录入异常或预处理不足。
单元测试与数据校验(数据传输完整性)
- 场景描述:后台服务把矩阵数据从服务A发送给服务B,B端收到数据后要验证是否与发送端一致。使用
==比较矩阵内容可以快速判断数据是否被篡改或传输错误。 - 为什么用这里的实现:
__eq__做了逐元素检查,简单可靠。
总结
- 我把你给出的片段整理成了一个完整的
Matrix类,实现了按行与按元素读写、相等检测、相加与相减等常用操作,并在接口中采用 1-based 索引以和你的原片段一致。 - 代码中注重错误处理(抛出明确异常)和拷贝策略(浅拷贝行返回、深拷贝赋值)以避免常见的引用陷阱。
- 示例覆盖了基础使用、相加相减、相等校验以及错误处理,并演示了和现实场景(图像差分、数据合并、校验)的结合。
- 算法复杂度方面,相等比较/相加/相减均为 (O(mn)),空间复杂度主要由矩阵大小决定,同样为 (O(mn))。
如果你愿意,我可以继续:
- 把索引改为 0-based,或同时支持 0-based/1-based;
- 增加矩阵乘法、转置、按行/列求和等功能;
- 把数值类型限制为
int/float并增加异常更严格的校验; - 或把矩阵实现优化为基于 NumPy 的实现以得到更高性能(如果你想用第三方库的话)。