一组集合不起作用是有充分理由的。想象一下(伪代码):
a = set(range(5)) # {0, 1, 2, 3, 4}
b = set(range(5, 10)) # {5, 6, 7, 8, 9}
c = set(range(5)) # {0, 1, 2, 3, 4}
# we've now created a set of sets, and then will drop `c` because it's redundant
d = set([a, b, c]) # {{0, 1, 2... }, {5, 6, 7...}}
# now we've changed a value inside the set, suddenly everything changes
a.add(6) # {{0, 1, 2..., 6}, {5, 6, 7...}}
# now we can re-add `c`
d.add(c) # {{0, 1, 2..., 6}, {5, 6, 7...}, {0, 1, 2...}}
除了元素如何突然消失的奇怪行为,或者如果它们是可变的,它们的行为会有所不同,还缺乏基于哈希的查找。
set 实现与 dict 实现非常相似,可以找到here。如果一个集合包含给定的键,这就是实现。注意它是如何计算对象的哈希值,找到第一个匹配项,然后从哈希值中查找的?
static int
set_contains_key(PySetObject *so, PyObject *key)
{
long hash;
setentry *entry;
if (!PyString_CheckExact(key) ||
(hash = ((PyStringObject *) key)->ob_shash) == -1) {
hash = PyObject_Hash(key);
if (hash == -1)
return -1;
}
entry = (so->lookup)(so, key, hash);
if (entry == NULL)
return -1;
key = entry->key;
return key != NULL && key != dummy;
}
现在,如果我们修改上面示例中的a,我们如何执行查找?唯一的解决方案是逐项查找,这将具有 O(n) 时间复杂度。
幸运的是,上面显示了一个简单的解决方案:不可变集合。幸运的是,Python 甚至有这个内置函数,frozenset。
使用frozenset,因为它是不可变的,所以可以计算哈希,既可以防止意外行为,又可以恢复我们的 O(1) 查找。
在这种情况下,我们可以这样做:
a = frozenset(range(5))
b = frozenset(range(5, 10))
c = frozenset(range(5))
d = set([a, b, c])
现在,d 将允许在单独的容器中查找单个项目的成员资格,因为frozenset 成员是不可变的。