摘要
我们的工作
- 给出了一些常见的不可hash报错的解决办法;
- 实现坐标Point类,并为其重载了加法和等号,实现了
Point
类 可hash化; - 实现 Point类 与 元组(tuple)进行判等,比如我们认为:
Point(1, 2)
等于(1, 2)
。
不可 hash的常见报错与解决
-
TypeError: unhashable type: 'list'
如下是一个 list 不能hash的代码s = set() a = [1, 2, 3] s.add(a) print(s)
将 list a 转成 tuple(a) 即可解决
a = tuple(a)
list 列表不支持hash,但元组 tuple支持hash。
-
类对象不可hash的报错
class Point: def __init__(self, x , y): self.x = x self.y = y def __repr__(self) -> str: return f"Point({self.x}, {self.y})" s = {(1, 2)} s.add(Point(1, 2)) print(s)
不实现 Point类 的
__eq__
方法,上述代码能够正常运行,能够把Point类对象添加进集合中。
由于我们没有给 Point 类实现__eq__
方法,任何一个Point的类对象与任何对象都不会相等。
比如下述代码中,两个一模一样的Point类对象都不相等。print(Point(1, 2) == Point(1, 2)) # False
实现
__eq__
方法之后,如果将Point
类对象添加到字典中,就会报 Point类不可hash的错误。这是因为__eq__
实现后导致原有的__hash__
方法失效,必须重写__hash__
方法。
比如运行以下代码,就可以看到 Point不可hash的错误。class Point: def __init__(self, x , y): self.x = x self.y = y def __repr__(self) -> str: return f"Point({self.x}, {self.y})" def __eq__(self, other) -> bool: return self.x == other.x and self.y == other.y s = set() s.add(Point(1, 2))
TypeError: unhashable type: ‘Point’
完整的可hash的代码,重写
__eq__
之后,必须重写__hash__
方法;
由于tuple
元组是可hash的,可将所有需要的属性放到一个元组中,如下代码所示class Point: def __init__(self, x , y): self.x = x self.y = y def __repr__(self) -> str: return f"Point({self.x}, {self.y})" def __eq__(self, other) -> bool: return self.x == other.x and self.y == other.y def __hash__(self) -> int: return hash((self.x, self.y))
以下是 Point
的一些扩展,如果您只是来浏览不可hash的解决办法,下述内容无需观看。
Point 类的代码
from typing import Sequence, Union
class Point:
def __init__(self, x: Union[int, Sequence], y: int = None):
if isinstance(x, Sequence):
assert len(x) == 2
x, y = x[0], x[1]
self.x = x
self.y = y
def __add__(self, other):
point = Point(self.x, self.y)
if isinstance(other, Point):
x = other.x
y = other.y
else:
x = other[0]
y = other[1]
point.x += x
point.y += y
return point
def __repr__(self):
return f"Point({self.x}, {self.y})"
def __eq__(self, other):
if isinstance(other, Point):
x = other.x
y = other.y
else:
x = other[0]
y = other[1]
return self.x == x and self.y == y
def __hash__(self):
return hash((self.x, self.y))
@property
def distance(self):
return self.x ** 2 + self.y ** 2
point 加法
通过 __add__(self, other)
函数重载了Point类的加法
- 不仅可以实现
Point
类之间的加法; - 还实现了和序列之间的加法,比如: list, tuple;
point = Point(1, 2)
print(point + (2, 3))
print(point + Point(2, 3))
point += (2, 3)
print(point)
输出结果
Point(3, 5)
Point(3, 5)
Point(3, 5)
类对象添加进集合或字典
集合和字典的key 在底层是同一个东西,这里只演示集合的操作。
我们在此通过简单的代码,为大家演示一个比较震撼的功能。
比如,在一些情况下,我们认为 Point(1, 2)
等于 (1, 2)
。
同时如果 (1, 2)
在一个字典中,那么 Point(1, 2)
不允许添加到该字典中。这些得益于重写 __eq__
和 __hash__
方法。
通过上述实现的Point
类已经可以实现上述功能了,这个功能极大的扩展了不同类对象之间的比较,在集合或字典的操作上,有很大的发展空间。
s = {(1, 2)}
point = Point(1, 2)
print(hash(point))
print(hash((1, 2)))
print(point in s)
输出结果:
-3550055125485641917
-3550055125485641917
True
通过输出结果可以看出 hash(point) == hash((1, 2))
要实现这个效果,不仅需要实现 __hash__
还需要实现 __eq__
。
若我们注释掉 __eq__
函数: 虽然point
可添加进字典,但point
不会和任何对象相等, print(point in s)
为 False
。
实战
以下是 在 leetcode上使用 Point 类的一个例子,如果您对算法没有要求,下述代码无需浏览。
https://leetcode.cn/problems/walking-robot-simulation/
用上述提供的 Point
类,做这道题会很方便
class Solution:
def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int:
# 要计算在某个方向上,被障碍物挡住
# 每走一步,都判断是否有障碍物,因为最多只能走8步,所以不会超时
obstacles = {tuple(item) for item in obstacles}
point = Point([0, 0])
# 方向数组的小标,即为当前的方向
direc_idx = 0
# 顺时针记录方向
direcs = (
(0, 1),
(1, 0),
(0, -1),
(-1, 0),
)
def turn_direc(cmd: int, direc_idx):
if cmd == -2:
direc_idx = (direc_idx - 1) % 4
elif cmd == -1:
direc_idx = (direc_idx + 1) % 4
return direc_idx
ans = point.distance
for cmd in commands:
# 刷新方向
direc_idx = turn_direc(cmd, direc_idx)
direc = direcs[direc_idx]
while cmd > 0:
if point + direc in obstacles:
break
point += direc
cmd -= 1
ans = max(ans, point.distance)
return ans