Python:使用集合检测重复项

时间:2011-05-12 13:54:11

标签: python python-3.x set

我需要将大量对象存储在内存中以便在Python中进行处理。具体来说,我正在尝试从大量对象中删除重复项。如果对象中的某个实例变量相等,我想将两个对象视为“相等”。所以,我认为最简单的方法是将我的所有对象插入到一个集合中,并覆盖__hash__方法,以便它散列我关注的实例变量。

因此,作为测试我尝试了以下内容:

class Person:
    def __init__(self, n, a):
        self.name = n
        self.age = a

    def __hash__(self):
        return hash(self.name)

    def __str__(self):
        return "{0}:{1}".format(self.name, self.age)

myset = set()
myset.add(Person("foo", 10))
myset.add(Person("bar", 20))
myset.add(Person("baz", 30))
myset.add(Person("foo", 1000)) # try adding a duplicate

for p in myset: print(p)

在这里,我定义了一个Person类,并且Person具有相同name变量的任何两个实例都是相同的,无论其他任何实例变量的值如何。不幸的是,这输出:

baz:30
foo:10
bar:20
foo:1000

请注意,foo出现两次,因此该程序未发现重复项。然而,hash(Person("foo", 10)) == hash(Person("foo", 1000))这个词是True。那么为什么这不能正确检测重复的Person个对象呢?

5 个答案:

答案 0 :(得分:11)

你忘了 define __eq__()

  

如果某个类未定义__cmp__()__eq__()方法,则不应定义__hash__()操作;如果它定义__cmp__()__eq__()但不定义__hash__(),则其实例将无法在散列集合中使用。如果一个类定义了可变对象并实现了__cmp__()__eq__()方法,那么它不应该实现__hash__(),因为hashable collection实现要求对象的哈希值是不可变的(如果对象的哈希值)更改,它将在错误的哈希桶中。)

答案 1 :(得分:4)

显然,一组必须处理哈希冲突。如果两个对象的散列匹配,则该集将使用==运算符对它们进行比较,以确保它们真的相等。在您的情况下,如果两个对象是同一个对象(用户定义类的标准实现),则只会产生True

长话短说:同时定义__eq__()以使其有效。

答案 2 :(得分:2)

散列函数不足以区分您必须实现比较函数的对象(即__eq__)。

答案 3 :(得分:1)

哈希函数有效地说“A可能等于B”或“A不等于B(肯定)”。

如果它说“可能等于”那么必须检查平等以确保,这就是为什么你还需要实现__eq__

尽管如此,定义__hash__可以通过使“A不等于B(肯定)”O(1)操作来显着加快速度。

然而,哈希函数必须始终遵循“哈希规则”:

  • “哈希规则”:相等的东西必须哈希到相同的值
  • (理由:否则我们会说“A不等于B(肯定)”,但事实并非如此)

例如,您可以按def __hash__(self): return 1对所有内容进行哈希处理。这仍然是正确的,但是效率很低,因为每次都必须检查__eq__,如果你有复杂的大型数据结构(例如大型列表,字典等),这可能是一个漫长的过程。

请注意,您在技术上遵循“哈希规则”,通过忽略实施def __hash__(self): return self.name中的年龄来执行此操作。如果鲍勃是一个20岁的人,鲍勃是另一个30岁的人,他们是不同的人(可能除非这是某种随时跟踪他们的年龄段跟踪计划),那么它们将散列为相同的值,并且必须与__eq__进行比较。这很好,但我会像这样实现它:

def __hash__(self):
    return hash( (self.name, self.age) )

请注意您的方式仍然正确。然而,在hash( (self.name, self.age) )Person("Bob", age=20)实际上是同一个人的世界中使用Person("Bob", age=30)会产生编码错误,因为哈希函数会说它们在等于功能不会(但可以忽略)。

答案 4 :(得分:0)

您还需要__ eq __()方法。