解决了常见不可hash的报错;实现Point类对象 hash化,给出如何将类对象添加集合或字典

摘要

我们的工作

  • 给出了一些常见的不可hash报错的解决办法;
  • 实现坐标Point类,并为其重载了加法和等号,实现了 Point类 可hash化;
  • 实现 Point类 与 元组(tuple)进行判等,比如我们认为:Point(1, 2) 等于 (1, 2)

不可 hash的常见报错与解决

  1. 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。

  2. 类对象不可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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jieshenai

为了遇见更好的文章

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值