Python自定义State类获取二维数组返回实例触发下标报错
8数码启发式搜索程序下标报错问题分析
问题表现
自定义State类封装二维列表格式的网格状态,类内实现getState()方法返回内部存储的二维数组。预期调用该方法拿到数组后可正常执行下标访问、元素交换等列表操作,但部分场景下获取到的值类型为<class '__main__.State'>而非预期的list,运行时抛出TypeError: 'State' object is not subscriptable错误。
经实测:
- 仅
find_h()函数内部调用state.getState()时可正常返回list类型 - 输入空白格坐标i=2、j=0运行代码时,报错触发点为
generateChildren()函数上移逻辑的网格元素交换步骤
报错栈信息如下:
Traceback (most recent call last):
line 122, in
driver()
line 115, in driver
generateChildren(current_state)
in generateChildren
grid[i-1][j], grid[i][j] = grid[i][j], grid[i-1][j]
TypeError: 'State' object is not subscriptable
根因分析
代码存在5处逻辑错误,共同导致报错及后续逻辑异常:
- State实例化传参类型错误:
generateChildren()的上移、左移、右移分支中,创建新节点的代码为new_state = State(state, 0, state),传入的第一个参数state本身是State类实例,而非二维列表,导致新节点的self.state属性存储的是State对象,后续访问该属性尝试做列表下标操作时必然触发类型错误。 - 当前节点二次包装错误:
driver()函数主循环中写了current_state = State(queue.pop(0)),而queue中存储的元素本身就是初始化完成的State实例,将该实例作为参数再次传入State构造函数,直接导致传入generateChildren()的current_state内部嵌套了一层State对象,是本次报错的直接触发点。 - 未做状态深拷贝:所有移动逻辑直接在原父节点的grid引用上修改元素,会直接篡改父节点、队列中其他节点、已访问列表中节点的状态,导致搜索过程中状态数据完全混乱。
- 右移逻辑边界判断错误:右移分支的判断条件误写为
0 <= i-1 < 3 and 0 <= j < 3,未判断j+1的坐标合法性,导致右移逻辑永远无法正常触发。 - 空白格坐标使用全局固定值:
generateChildren()中直接使用全局输入的blank_i、blank_j作为当前节点的空白格坐标,没有根据当前处理的网格动态计算0的位置,节点移动1次后空白格坐标就会和实际值不匹配。
修复方案
针对上述错误逐点修正即可:
- 所有创建
State实例的位置,第一个参数必须传入二维列表类型的网格数据,禁止直接传入State对象 - 移除
driver()中对弹出队列节点的二次State包装,直接将弹出的State实例赋值给current_state - 生成子节点时,先对父节点的二维网格做深拷贝,在拷贝出的独立网格上执行元素交换,避免修改父节点状态
- 修正右移分支的边界判断逻辑,判断j+1是否在0-2的合法范围内
- 废弃全局固定的空白格坐标,每次处理当前节点时,先遍历网格找到值为0的位置作为当前空白格坐标,再执行移动逻辑
修正后的核心代码片段如下:
import copy def generateChildren(state): # 动态计算当前状态的空白格坐标 grid = state.getState() i, j = 0, 0 for x in range(3): for y in range(3): if grid[x][y] == 0: i, j = x, y break # 上移 if 0 <= i-1 < 3: new_grid = copy.deepcopy(grid) new_grid[i-1][j], new_grid[i][j] = new_grid[i][j], new_grid[i-1][j] new_state = State(new_grid, 0, state) new_state.h = find_h(new_state) queue.append(new_state) # 下移 if 0 <= i+1 < 3: new_grid = copy.deepcopy(grid) new_grid[i+1][j], new_grid[i][j] = new_grid[i][j], new_grid[i+1][j] new_state = State(new_grid, 0, state) new_state.h = find_h(new_state) queue.append(new_state) # 左移 if 0 <= j-1 < 3: new_grid = copy.deepcopy(grid) new_grid[i][j-1], new_grid[i][j] = new_grid[i][j], new_grid[i][j-1] new_state = State(new_grid, 0, state) new_state.h = find_h(new_state) queue.append(new_state) # 右移 if 0 <= j+1 < 3: new_grid = copy.deepcopy(grid) new_grid[i][j+1], new_grid[i][j] = new_grid[i][j], new_grid[i][j+1] new_state = State(new_grid, 0, state) new_state.h = find_h(new_state) queue.append(new_state) def driver(): goal = [[1, 2, 3], [4, 5, 6], [7, 8, 0]] start_grid = [[3, 7, 6], [8, 4, 2], [0, 1, 5]] start = State(start_grid) h = find_h(start) start.setH(h) global sol queue.append(start) while queue: queue.sort(key=lambda x: x.h) # 移除多余的State二次包装 current_state = queue.pop(0) if goalstate(current_state, goal): sol += 1 traceback(current_state) break else: if inVisited(current_state): continue visited.append(current_state) generateChildren(current_state)
注:原代码visited列表只做了查询没有写入节点,修复时补充了访问后将节点加入visited的逻辑,避免重复搜索死循环。
内容的提问来源于stack exchange,提问作者eme
相关产品推荐
相关产品推荐

