向集合添加相同元组却生成元组排列项的问题排查
尝试生成由5个二元整数元组组成的元组的唯一排列,通过set.add()方法去重,但执行代码时发现:即使打印出要添加的列表与首次完全相同,集合中仍会出现元素重排的新项;循环8次后集合中出现8个不同排列项,而预期应仅保留1个唯一项。
代码如下:
def generate_each_orientation(self): self.orientations = set() working = self.raw[:] Piece.normalize(working) for i in range(8): print("Iteration ",i+1) print("List to be added to set: ", working) self.orientations.add(tuple(working)) print("Set after adding list:\n",self.orientations) [Tile.rotate(t) for t in working] Piece.normalize(working) if i == 3: [Tile.flip(t) for t in working] Piece.normalize(working)
执行输出示例:
第一次迭代:
Iteration 1
List to be added to set: [(0, 0), (1, 0), (1, -1), (1, 1), (2, 0)]
Set after adding list:
{((0, 0), (1, 0), (1, -1), (1, 1), (2, 0))}
第二次迭代:
Iteration 2
List to be added to set: [(0, 0), (1, 0), (1, -1), (1, 1), (2, 0)]
Set after adding list:
{((1, -1), (1, 0), (2, 0), (0, 0), (1, 1)), ((0, 0), (1, 0), (1, -1), (1, 1), (2, 0))}
- 元素类型误解:你打印
working时看到的是Tile对象的坐标字符串(由__repr__方法输出),但working实际存储的是Tile类的实例对象,而非二元整数元组。执行tuple(working)生成的是包含Tile对象引用的元组,而非坐标元组的元组。 - 可变对象的副作用:
Tile.rotate(t)和Tile.flip(t)是原地修改Tile对象的内部坐标属性,而集合中存储的元组引用了这些可变对象。当后续修改Tile对象状态时,集合中已存在的元组的显示内容会跟着变化(因为__repr__输出的是当前对象状态),导致你误以为集合中新增了“重排项”,但实际是原有元组的元素状态被修改了。 - 集合去重逻辑失效:
set依赖元素的不可变性和正确的__hash__/__eq__实现判断唯一性。如果Tile类未基于坐标实现__hash__和__eq__,默认会以对象内存地址作为判断依据——即使两个Tile对象坐标相同,也会被视为不同元素;同时,可变对象状态变化后,集合的哈希表会出现不一致,无法正确识别重复项。
方案一(推荐):添加不可变的坐标元组到集合
直接提取Tile对象的坐标(二元整数元组),生成不可变的元组添加到集合,彻底避免可变对象的副作用。修改添加集合的代码:
# 假设Tile对象通过coords属性存储(x, y)二元元组 self.orientations.add(tuple(t.coords for t in working)) # 若Tile类实现了__iter__方法可直接解包坐标,也可使用: # self.orientations.add(tuple(tuple(t) for t in working))
这种方式下,集合存储的是纯整数元组,哈希值稳定,能准确判断唯一性,不受Tile对象状态变化或列表顺序调整的影响。
方案二:修正Tile类的哈希与相等性实现
若必须存储Tile对象的元组,需在Tile类中基于坐标实现__hash__和__eq__,确保坐标相同的Tile被视为同一元素:
class Tile: def __init__(self, x, y): self.coords = (x, y) # 用不可变元组存储坐标 def rotate(self): # 原地修改坐标示例,确保修改后更新coords x, y = self.coords self.coords = (-y, x) def flip(self): # 原地翻转坐标示例 x, y = self.coords self.coords = (-x, y) def __hash__(self): # 基于坐标生成哈希值 return hash(self.coords) def __eq__(self, other): # 基于坐标判断相等 if not isinstance(other, Tile): return False return self.coords == other.coords def __repr__(self): # 输出坐标字符串,保持打印一致性 return str(self.coords)
注意:此方案仍需确保Tile对象的状态修改不会破坏哈希的稳定性,相比方案一,不可变性更弱,潜在问题更多。
内容的提问来源于stack exchange,提问作者user206545

