如何__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应该是相同的,所以他们应该算如相同的对象,不应该添加设置。 但他们做的! 林卡住。
平等字典/集的目的取决于平等所界定__eq__
。 但是,要求是比较相等的对象具有相同的哈希值,这就是为什么你需要__hash__
。 见这个问题对于一些类似的讨论。
哈希本身并不能决定两个对象是否算作字典一样。 哈希就像一个“捷径”,只有一个工作方式:如果两个对象有不同的哈希,他们肯定是不相等的。 但如果他们有相同的哈希,他们仍然可能是不相等的。
在你的榜样,你定义__hash__
和__eq__
做不同的事情。 哈希只取决于对多米诺数字的相加,而是平等取决于两个个体数(按顺序)。 这是合法的,因为它仍然是平等的多米诺骨牌有平等的哈希值的情况。 然而,就像我上面说的,这并不意味着等于森骨牌将被视为相等。 一些不平等的多米诺骨牌将仍然具有相同的哈希值。 但平等仍然被确定__eq__
和__eq__
仍然着眼于这两个数字,为了,所以这是决定他们是否相等。
在我看来,适当的事情在你的情况做的是同时定义__hash__
和__eq__
依赖于有序对---即先比较两个数字越大,则比较少。 这将意味着,2-1和1-2将被视为相同。
哈希只是一个提示,以帮助安排的Python的对象。 当寻找一些对象foo
在一组,但它仍然必须检查每个对象的集合相同的哈希作为foo
。
这就像字母表中的每个字母一个书架。 说你要一本新书添加到您的收藏, 只要你不具备的,它已经复印件; 你先去货架相应的字母。 但你必须看看每个书架上的书,并把它比作一个在你的手,看它是否是相同的。 只是因为这里的货架上已经有些事情,你不会放弃新的书。
如果你想使用一些其他的值过滤掉“复制”,然后使用映射多米诺骨牌的总价值你看到的第一张骨牌的字典。 不要颠覆内置Python的行为意味着完全不同的东西。 (正如你已经发现,它并没有在这种情况下工作,反正。)
用于散列函数的要求是,如果x == y
为两个值,则hash(x) == hash(y)
相反不一定是真实的。
你可以很容易地看到为什么这是通过考虑串的散列的情况。 可以说,该hash(str)
返回一个32位的数字,并且我们散列串比长4个字符(即,包含超过32个位)。 还有更多可能的字符串比有可能哈希值,所以一些不相等的字符串必须共享相同的哈希值(这是对一个应用鸽巢原理 )。
Python的集合被实现为哈希表。 当检查对象是否是集合中的一员,它会调用其散列函数,并根据结果选择一个桶,然后使用等号,看它是否匹配任何桶中的项目。
有了您的实现中,2-2和1-3的多米诺骨牌将在散列桶结束了,但都比不上相等。 因此,既可以添加到集中。
您可以在Python的在阅读有关此数据模型文档 ,但简短的回答是,你可以重写你的散列函数为:
def __hash__(self):
return hash(tuple(sorted((self.one, self.two))))
我喜欢伊布提供的答案的声音,但我有困难想象的实现。 下面是我的理解,解释和执行伊布提供的答案。
- 使用两个Domino值作为字典的关键之和。
- 将任多米诺值作为字典值。
例如,给定的多米诺“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'}
虽然我们只存储在Domino对单个值,另一个值可以很容易地从字典中键和值来计算:
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
但是,因为两对不同的可能有相同的总,我们需要多个值存储针对单个键。 所以,我们对存储一个键(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]}
“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])
正常,我们得到的总和(我们的字典键)。 如果它不是在字典中,则对可以不存在。 如果在字典中,我们必须检查,看看我们对任何一个值字典中的“桶”存在。 让我们插入此支票存入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)
文章来源: Python. Identity in sets of objects. And hashing