gpt4 book ai didi

python - 如果我们仍然需要检查每个项目,哈希的含义是什么?

转载 作者:太空狗 更新时间:2023-10-30 00:28:56 24 4
gpt4 key购买 nike

我们知道tuple对象是不可变的,因此是散列的。我们还知道lists是可变的和不可散列的。
这很容易说明

>>> set([1, 2, 3, (4, 2), (2, 4)])
{(2, 4), (4, 2), 1, 2, 3}

>>> set([1, 2, 3, [4, 2], [2, 4]])
TypeError: unhashable type: 'list'

现在,在这个上下文中 hash的含义是什么,如果为了检查唯一性(例如,在构建集合时),我们仍然必须检查集合中的任何iterable中的每个单独项?
我们知道两个对象可以具有相同的 hash值,但仍然是不同的。因此, hash仅用于比较对象是不够的。那么,散列的意义是什么?为什么不直接检查iterables中的每个项目呢?
我的直觉是这可能是因为
hash只是一个(很快)的初步比较。如果 hashes不同,我们知道对象是不同的。
表示对象是可变的。当与其他对象比较时,这应该足以引发一个异常:在特定的时间,对象可以是相等的,但也许以后,它们不是。
我的方向对吗?还是我错过了重要的一部分?
谢谢你

最佳答案

现在,hash在这个上下文中的意义是什么,如果,为了检查唯一性(例如,在构建集合时),我们仍然必须检查集合中的任何iterable中的每个单独项?
是的,但是哈希用于两个对象可以相等的情况下进行保守估计,也用于分配一个“桶”到一个项目。如果哈希函数是精心设计的,那么很可能(不确定),大多数,如果不是全部,最终在不同的桶,因此,因此,我们使成员检查/插入/删除/…算法平均运行在恒定时间O(1),而不是O(n),这是典型的列表。
所以,你的第一个答案是部分正确的,尽管你必须考虑到桶肯定会提高性能,而且实际上比保守检查更重要。
背景
注:我将在这里使用一个简化的模型,使原理清晰,实际上字典的实现更为复杂。例如散列在这里只是一些数字,显示了普林西比。
哈希集和字典被实现为一个“bucket”数组。元素的散列决定了我们将元素存储在哪个bucket中。如果元素的数量增加,那么bucket的数量就会增加,字典中已经存在的元素通常会被“重新分配”到bucket中。
例如,空字典在内部可能看起来像:

+---+
| |
| o----> NULL
| |
+---+
| |
| o----> NULL
| |
+---+

所以两个bucket,如果我们添加一个元素 'a',那么散列就是 123。让我们考虑一个简单的算法,将一个元素分配给一个桶,这里有两个桶,所以我们会给元素分配一个偶数散列给第一个桶,而一个奇数散列给第二个桶。由于 'a'的散列是奇数,因此我们将 'a'分配给第二个bucket:
+---+
| |
| o----> NULL
| |
+---+
| | +---+---+
| o---->| o | o----> NULL
| | +-|-+---+
+---+ 'a'

所以这意味着如果我们现在检查 'b'是否是字典的成员,我们首先计算 hash('b'),也就是 456,因此如果我们把它添加到字典中,它将在第一个bucket中。因为第一个bucket是空的,所以我们不必在第二个bucket中寻找元素来确定 'b'不是成员。
例如,如果我们想检查 'c'是否是一个成员,我们首先生成 'c'的散列,即 789,因此我们再次将其添加到第二个bucket,例如:
+---+
| |
| o----> NULL
| |
+---+
| | +---+---+ +---+---+
| o---->| o | o---->| o | o----> NULL
| | +-|-+---+ +-|-+---+
+---+ 'c' 'a'

因此,现在如果我们再次检查 'b'是否是一个成员,我们将查看第一个bucket,然后,我们再也不必遍历 'c''a'来确定 'b'不是字典的成员。
当然,现在有人可能会争辩说,如果我们继续添加更多的字符,比如 'e''g'(这里我们认为这些字符有一个奇怪的散列),那么这个bucket将非常满,因此如果我们稍后检查 'i'是否是成员,我们仍然需要遍历元素。但是,如果元素的数量增加,通常存储桶的数量也会增加,并且字典中的元素将被分配一个新的存储桶。
例如,如果我们现在想将 'd'添加到字典中,字典可能会注意到插入后的元素数 3大于桶的数量 2,因此我们创建了一个新的桶数组:
+---+
| |
| o----> NULL
| |
+---+
| |
| o----> NULL
| |
+---+
| |
| o----> NULL
| |
+---+
| |
| o----> NULL
| |
+---+

我们重新分配成员 'a''c'。现在所有具有哈希< cc> > h的元素将被分配给第一个桶, h % 4 == 0到第二个桶, h % 4 == 1到第三个桶,并且 h % 4 == 2到最后一个桶。这意味着hash h % 4 == 3'a'将存储在最后一个bucket中,hash 123'c'将存储在第二个bucket中,因此:
+---+
| |
| o----> NULL
| |
+---+
| | +---+---+
| o---->| o | o----> NULL
| | +-|-+---+
+---+ 'c'
| |
| o----> NULL
| |
+---+
| | +---+---+
| o---->| o | o----> NULL
| | +-|-+---+
+---+ 'a'

然后将hash 789'b'添加到第一个bucket中,这样:
+---+
| | +---+---+
| o---->| o | o----> NULL
| | +-|-+---+
+---+ 'b'
| | +---+---+
| o---->| o | o----> NULL
| | +-|-+---+
+---+ 'c'
| |
| o----> NULL
| |
+---+
| | +---+---+
| o---->| o | o----> NULL
| | +-|-+---+
+---+ 'a'

因此,如果我们想检查cc的成员数,我们计算散列,知道如果在字典中是cc,我们必须在第三个桶中搜索,并在那里找到它。如果我们寻找 456'a',也会发生同样的情况(但是使用不同的桶),如果我们寻找 'a'(这里用哈希 'b'),那么我们将在第三个桶中进行搜索,并且永远不必检查单个元素的相等性,以知道它不是字典的一部分。
如果我们想检查 'c'是否是一个成员,那么我们计算 'd'的散列(这里是 12),并在第二个bucket中搜索。因为bucket不是空的,所以我们开始遍历它。
对于桶中的每个元素(这里只有一个元素),算法首先查看我们搜索的密钥,而节点中的密钥引用相同的对象(两个不同的对象可以是相等的),因为情况并非如此,我们不能声称 'e'在字典中。
接下来我们将比较我们搜索的密钥的散列和节点的密钥。大多数字典实现(如果我正确地回忆了),也可以将散列存储在列表节点中。因此,这里检查是否 'e'等于< > >,因为事实并非如此,我们知道 345和< >是不一样的。如果比较这两个对象比较昂贵,那么我们可以节省一些周期。
如果散列是相等的,这并不意味着元素是相等的,因此,在这种情况下,我们将检查这两个对象是否相等,如果是这样的话,我们知道元素在字典中,否则,我们知道它不是。

关于python - 如果我们仍然需要检查每个项目,哈希的含义是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53160472/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com