__hash__和__eq__如何在集合中使用标识? 例如,一些代码应该有助于解决一些多米诺骨牌:
class foo(object):
def __init__(self, one, two):
self.one = one
self.two = two
def __eq__(self,other):
if (self.one == other.one) and (self.two == other.two): return True
if (self.two == other.one) and (self.one == other.two): return True
return False
def __hash__(self):
return hash(self.one + self.two)
s = set()
for i in range(7):
for j in range(7):
s.add(foo(i,j))
len(s) // returns 28 Why?
如果我只使用__eq __()len(s)等于49.它可以,因为我理解对象(例如1-2和2-1)不相同,但代表相同的多米诺骨牌。所以我添加了哈希函数 现在它按照我想要的方式工作,但我不明白一件事:1-3和2-2的哈希应该是相同的,所以它们应该像相同的对象一样计算,不应该添加到集合中。但是他们做到了!我卡住了。
答案 0 :(得分:4)
dict / set目的的平等取决于__eq__
定义的相等性。但是,要求比较相等的对象具有相同的哈希值,这就是您需要__hash__
的原因。有关类似的讨论,请参阅this question。
散列本身并不确定两个对象在字典中是否相同。哈希就像一个只能以一种方式工作的“快捷方式”:如果两个对象有不同的哈希值,它们绝对不相等;但如果它们具有相同的哈希值,它们可能仍然不相等。
在您的示例中,您定义了__hash__
和__eq__
来执行不同的操作。哈希值仅取决于多米诺骨牌上数字的总和,但相等性取决于两个单独的数字(按顺序)。这是合法的,因为相同的多米诺骨牌仍然具有相同的哈希值。但是,正如我上面所说,并不意味着等额多米诺骨牌将被视为平等。一些不相等的多米诺骨牌仍然会有相同的哈希。但是,平等仍由__eq__
决定,而__eq__
仍按顺序查看这两个数字,这就决定了它们是否相等。
在我看来,在你的情况下适当的做法是定义__hash__
和__eq__
以依赖有序对 - 即,首先比较两个数字中的较大者,然后比较较小的数字。这意味着2-1和1-2将被视为相同。
答案 1 :(得分:2)
哈希只是一个帮助Python安排对象的提示。在集合中查找某个对象foo
时,仍然必须使用与foo
相同的散列检查集合中的每个对象。
就像为每个字母表中的字母设置一个书架。假设您要在您的收藏中添加新书,仅如果您还没有它的副本;你先去书架上找相应的信。但是你必须看看书架上的每本书并将它与你手中的书进行比较,看它是否相同。你不会因为架子上已有东西而放弃新书。
如果您想使用其他一些值来过滤掉“重复”,那么请使用将多米诺骨牌总值映射到您看到的第一张多米诺骨牌的字典。不要颠覆内置的Python行为意味着完全不同的东西。 (正如你所发现的,无论如何它在这种情况下都不起作用。)
答案 2 :(得分:1)
哈希函数的要求是,如果x == y
为两个值,则为hash(x) == hash(y)
。相反的情况不一定是真的。
通过考虑字符串的散列,您可以很容易地了解为什么会出现这种情况。假设hash(str)
返回一个32位数字,我们正在散列长度超过4个字符的字符串(即包含超过32位)。可能的字符串比可能的哈希值更多,因此一些不相等的字符串必须共享相同的哈希值(这是pigeonhole principle的应用程序)。
Python集实现为哈希表。当检查对象是否是该集合的成员时,它将调用其散列函数并使用结果来选择存储桶,然后使用相等运算符来查看它是否与存储桶中的任何项匹配。
随着你的实现,2-2和1-3多米诺骨牌将最终进入哈希桶,但他们不比较相等。因此,两者都可以添加到集合中。
答案 3 :(得分:0)
您可以在Python data model documentation中阅读此内容,但简短的回答是您可以将哈希函数重写为:
def __hash__(self):
return hash(tuple(sorted((self.one, self.two))))
答案 4 :(得分:0)
我喜欢Eevee提供的答案的声音,但我很难想象一个实现。以下是我对Eevee提供的答案的解释,解释和实施。
例如,给定多米诺骨牌'12',总和为3,因此字典键将为3.然后我们可以选择值(1或2)存储在该位置(我们将选择第一个)价值,1)。
domino_pairs = {}
pair = '12'
pair_key = sum(map(int, pair))
domino_pairs[pair_key] = int(pair[0]) # Store the first pair's first value.
print domino_pairs
输出:
{3: '1'}
虽然我们只存储多米诺骨牌对中的单个值,但可以从字典键和值中轻松计算另一个值:
pair = '12'
pair_key = sum(map(int, pair))
domino_pairs[pair_key] = int(pair[0]) # Store the first pair's first value.
# Retrieve pair from dictionary.
print pair_key - domino_pairs[pair_key] # 3-1 = 2
输出:
2
但是,由于两个不同的对可能具有相同的总数,因此我们需要针对单个密钥存储多个值。因此,我们针对单个密钥存储值列表(即两对的总和)。把它放到一个函数中:
def add_pair(dct, pair):
pair_key = sum(map(int, pair))
if pair_key not in dct:
dct[pair_key] = []
dct[pair_key].append(int(pair[0]))
domino_pairs = {}
add_pair(domino_pairs, '22')
add_pair(domino_pairs, '04')
print domino_pairs
输出:
{4: [2, 0]}
这是有道理的。两对总和为4,但每对中的第一个值不同,因此我们存储两者。到目前为止的实现将允许重复:
domino_pairs = {}
add_pair(domino_pairs, '40')
add_pair(domino_pairs, '04')
print domino_pairs
输出
{4: [4, 0]}
Dominos中的'p''40'和'04'是相同的,所以我们不需要同时存储它们。我们需要一种检查重复的方法。为此,我们将定义一个新函数has_pair
:
def has_pair(dct, pair):
pair_key = sum(map(int, pair))
if pair_key not in dct:
return False
return (int(pair[0]) in dct[pair_key] or
int(pair[1]) in dct[pair_key])
正常情况下,我们得到总和(我们的字典键)。如果它不在字典中,则该对不能存在。如果它在字典中,我们必须检查字段'bucket'中是否存在 值。我们将此检查插入add_pair
,因此我们不添加重复的多米诺骨牌对:
def add_pair(dct, pair):
pair_key = sum(map(int, pair))
if has_pair(dct, pair):
return
if pair_key not in dct:
dct[pair_key] = []
dct[pair_key].append(int(pair[0]))
现在添加重复的多米诺骨牌对正常工作:
domino_pairs = {}
add_pair(domino_pairs, '40')
add_pair(domino_pairs, '04')
print domino_pairs
输出:
{4: [4]}
最后,打印函数显示如何仅存储多米诺骨牌对的总和以及来自同一对的单个值,与存储该对本身的方式相同:
def print_pairs(dct):
for total in dct:
for a in dct[total]:
a = int(a)
b = int(total) - int(a)
print '(%d, %d)'%(a,b)
测试:
domino_pairs = {}
add_pair(domino_pairs, '40')
add_pair(domino_pairs, '04')
add_pair(domino_pairs, '23')
add_pair(domino_pairs, '50')
print_pairs(domino_pairs)
输出:
(4, 0)
(2, 3)
(5, 0)