从零实现一个可加减的Matrix矩阵类:支持索引、相等判断与实际场景应用

摘要

本文基于你给出的一段矩阵类(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

题解代码分析

接下来逐一解释上面代码的关键点和设计考虑。

  1. 构造 __init__

    • 要求 rows 和 cols 为正整数,避免不合理构造。
    • 内部用 self.matrix 存列表的列表。使用 copy.deepcopy(fill) 保证如果 fill 是可变对象也被安全复制(虽然通常是数值)。
    • 设计为 1-based 索引对外接口(和题目保持一致),但内部仍用 0-based 列表索引。
  2. shape 属性:

    • 返回 (rows, cols),便于外部检查维度相等。
  3. __getitem__

    • 支持 m[i](返回第 i 行)与 m[i, j](返回第 i 行第 j 列)这两种常见访问方式。
    • m[i] 返回行时用 .copy(),避免用户拿到引用后直接改动 self.matrix 的内部数据(如果需要改动行,建议使用 m[i] = ... 显式赋值)。
    • 对索引超界进行检查并抛出 IndexError
  4. __setitem__

    • 两种赋值方式都支持:按行赋值和按元素赋值。
    • 按行赋值时,要求提供的 value 是 list 或 tuple,并且长度须匹配列数,并深拷贝以防止后续外部修改影响内部矩阵。
    • 按元素赋值时,直接替换对应位置的值。
    • 设计上明确区分了错误类型(TypeErrorValueErrorIndexError),便于上层调用者捕获与调试。
  5. __eq__

    • 首先检查对象类型是否为 Matrix(或你可以放宽要求,但这里做严格检查),然后检查 shape,最后逐元素比较。只要有一个元素不同则返回 False,否则 True
  6. __add____sub__

    • 这里都要求另一个操作数是 Matrix,并且维度相同。返回新的 Matrix 对象而不是修改原矩阵(函数式风格,更安全)。
    • 在内部通过循环逐元素赋值。如果遇到维度不同,抛出 ValueError,比返回字符串更 Pythonic、更利于异常处理与调试。
  7. __str__

    • 提供一个简单的行格式打印,便于调试与展示。
  8. 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 的实现以得到更高性能(如果你想用第三方库的话)。
相关推荐
qq_ddddd1 天前
对于随机变量x1, …, xn,其和的范数平方的期望不超过n倍各随机变量范数平方的期望之和
人工智能·神经网络·线性代数·机器学习·概率论·1024程序员节
py有趣1 天前
LeetCode学习之0矩阵
学习·leetcode·矩阵
郝学胜-神的一滴2 天前
Cesium绘制线:从基础到高级技巧
前端·javascript·程序人生·线性代数·算法·矩阵·图形渲染
前端小L2 天前
动态规划的“升维”之技:二维前缀和,让矩阵查询“降维打击”
线性代数·矩阵
爱学习的小鱼gogo3 天前
pyhton 螺旋矩阵(指针-矩阵-中等)含源码(二十六)
python·算法·矩阵·指针·经验·二维数组·逆序
智能化咨询3 天前
矩阵的奇异值分解(SVD)核心原理与图形学基础应用
矩阵
西***63473 天前
从信号处理到智能协同:高清混合矩阵全链路技术拆解,分布式系统十大趋势抢先看
网络·分布式·矩阵
AIGC_北苏3 天前
大语言模型,一个巨大的矩阵
人工智能·语言模型·矩阵
HVACoder3 天前
复习下线性代数,使用向量平移拼接两段线
c++·线性代数·算法