如何基于MutableSequence实现无numpy依赖的9×9数独二维数组类
如何基于
collections.abc.MutableSequence实现数独数组类? 背景
几天前我发现了一个通过继承collections.abc.MutableSequence实现自定义列表类的示例,代码如下:
class TypedList(MutableSequence): def __init__(self, oktypes, *args): self.oktypes = oktypes self.list = list() self.extend(list(args)) def check(self, v): if not isinstance(v, self.oktypes): raise TypeError(v) def __len__(self): return len(self.list) def __getitem__(self, i): return self.list[i] def __delitem__(self, i): del self.list[i] def __setitem__(self, i, v): self.check(v) self.list[i] = v def insert(self, i, v): self.check(v) self.list.insert(i, v) def __str__(self): return str(self.list)
使用示例:
tl = TypedList(int) # 正常执行 tl.append(1) # 会触发TypeError tl.append('1')
需求与问题
我想以类似方式实现一个继承自MutableSequence的二维SudokuArray类,用于管理数独游戏的数组。以下是我写的一个不可行的版本:
class SudokuArray(MutableSequence): def __init__(self, n_rows=9, n_columns=9, init_value=None): self.n_rows = n_rows self.n_columns = n_columns self.array = [[init_value for _ in range(0, self.n_columns, 1)] for _ in range(0, self.n_rows, 1)] def check(self, row_number, column_number, number): if number in self.get_row_values(row_number) or\ number in self.get_column_values(column_number) or\ number in self.get_nonet_values(row_number, column_number): raise ExistentNumberError("Existent number in row, column or nonet", number, row_number, column_number) def __len__(self): return self.n_rows, self.n_columns def __getitem__(self, row_number, column_number): return self.array[column_number][row_number] def __delitem__(self, row_number, column_number): del self.array[column_number][row_number] def __setitem__(self, row_number, column_number, number): self.check(number, row_number, column_number) self.array[column_number][row_number] = number def insert(self, row_number, column_number, number): self.check(row_number, column_number, number) self.array.insert(row_number, column_number, number) def __str__(self): return str(self.array) def get_row_values(self, row_number): # todo implement get_row_values method pass def get_column_values(self, column_number): # todo implement get_column_values method pass def get_nonet_values(self, row_number, column_number): # todo implement get_nonet_values method pass class ExistentNumberError(Exception): def __init__(self, message, number, row_number, column_number): super().__init__(message) self.number = number self.row_number = row_number self.column_number = column_number
使用示例:
sudoku_array = SudokuArray() sudoku_array[0][0] = 1 # 会触发ExistentNumberError sudoku_array[0][0] = 1
我不想用numpy.array,因为9×9的数组用numpy过于复杂,而且希望避免外部依赖。请问有什么可行的实现思路?
可行实现思路与代码
核心问题修正
MutableSequence是为一维序列设计的抽象基类,它的方法(如__getitem__、__setitem__)只接受单一索引参数,直接实现二维访问会违反接口约定。正确的做法是让SudokuArray作为行的序列,每个行是一个自定义序列类,负责列的访问与校验。
另外,数独数组是固定大小的,不需要insert、append这类动态增删元素的方法,可直接抛出NotImplementedError禁用。
完整实现代码
from collections.abc import MutableSequence class ExistentNumberError(Exception): def __init__(self, message, number, row_number, column_number): super().__init__(message) self.number = number self.row_number = row_number self.column_number = column_number class SudokuRow(MutableSequence): def __init__(self, parent, row_idx): self.parent = parent self.row_idx = row_idx self._data = [None] * parent.n_columns def check(self, col_idx, value): if value is None: return # 检查行、列、3x3宫 if value in self._data or \ value in self.parent.get_column_values(col_idx) or \ value in self.parent.get_nonet_values(self.row_idx, col_idx): raise ExistentNumberError( f"数字{value}已存在于行{self.row_idx}、列{col_idx}或对应3x3宫", value, self.row_idx, col_idx ) def __len__(self): return len(self._data) def __getitem__(self, col_idx): return self._data[col_idx] def __setitem__(self, col_idx, value): self.check(col_idx, value) self._data[col_idx] = value def __delitem__(self, col_idx): # 数独不允许删除元素,直接抛出错误 raise NotImplementedError("数独数组不支持删除操作") def insert(self, col_idx, value): # 数独是固定大小,不允许插入 raise NotImplementedError("数独数组不支持插入操作") def __str__(self): return str(self._data) class SudokuArray(MutableSequence): def __init__(self, n_rows=9, n_columns=9): if n_rows != 9 or n_columns !=9: raise ValueError("标准数独数组必须是9x9") self.n_rows = n_rows self.n_columns = n_columns self._rows = [SudokuRow(self, i) for i in range(n_rows)] def get_row_values(self, row_idx): return [v for v in self._rows[row_idx] if v is not None] def get_column_values(self, col_idx): return [self._rows[r][col_idx] for r in range(self.n_rows) if self._rows[r][col_idx] is not None] def get_nonet_values(self, row_idx, col_idx): # 计算3x3宫的起始行和列 start_row = (row_idx // 3) * 3 start_col = (col_idx // 3) * 3 values = [] for r in range(start_row, start_row+3): for c in range(start_col, start_col+3): val = self._rows[r][c] if val is not None: values.append(val) return values def __len__(self): return self.n_rows def __getitem__(self, row_idx): return self._rows[row_idx] def __setitem__(self, row_idx, value): # 不允许直接替换整行,如需修改请逐列操作 raise NotImplementedError("不允许直接替换整行,请逐列修改") def __delitem__(self, row_idx): raise NotImplementedError("数独数组不支持删除行") def insert(self, row_idx, value): raise NotImplementedError("数独数组不支持插入行") def __str__(self): return "\n".join(str(row) for row in self._rows)
使用示例
sudoku = SudokuArray() # 正常赋值 sudoku[0][0] = 1 # 尝试在同一行重复赋值,触发错误 try: sudoku[0][1] = 1 except ExistentNumberError as e: print(e) # 输出:数字1已存在于行0、列1或对应3x3宫 # 尝试在同一列赋值 sudoku[1][0] = 2 try: sudoku[2][0] = 2 except ExistentNumberError as e: print(e) # 输出:数字2已存在于行2、列0或对应3x3宫 # 尝试在同一3x3宫赋值 sudoku[0][1] = 3 try: sudoku[1][2] = 3 except ExistentNumberError as e: print(e) # 输出:数字3已存在于行1、列2或对应3x3宫
说明
- 实现严格遵循
MutableSequence接口约定,通过嵌套的SudokuRow类实现二维访问。 - 所有违反数独规则的赋值操作都会触发
ExistentNumberError。 - 禁用了数独不需要的动态增删操作,保证数组大小固定为9x9。
- 完全基于Python标准库,无外部依赖。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

