具有可哈希键的自定义dict无法处理递归结构

时间:2018-06-22 01:06:49

标签: python python-3.x dictionary hash hashmap

我需要一个类似字典的结构,该结构可以采用无法散列的键并将它们映射为一个值。我有两个原因需要它:

  1. 遍历列表时检查在 O(1)中是否已经看到某项

  2. 将每个项目映射到一个标识符,例如一个字符

创建的类似dict的结构将在此过程后被丢弃,因此一旦对键进行突变就无法使用。

示例

In [8]: np.arange(1,10)
Out[8]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
In [9]: np.arange(3*3)
Out[9]: array([0, 1, 2, 3, 4, 5, 6, 7, 8])

实施

tl; dr我实现了一些行不通的方法。如果您看到实现此目标的规范方法,请跳过该部分。

现在,我编写了一个包装器类,该包装器实现了一个d = MutableKeyDict() d[[1, 2, 3]] = 'a' print([1, 2, 3] in d) # True print((1, 2, 3) in d) # False 方法,该方法依赖于等同于对其数据进行哈希处理的不可变类型。

__hash__

这使我能够编写一个自定义dict类的草稿,该类使用class ForcedHashable: @staticmethod def hashable(obj): try: hash(obj) return obj except TypeError: if isinstance(obj, (list, tuple)): return tuple(ForcedHashable.hashable(o) for o in obj) elif isinstance(obj, set): return frozenset(ForcedHashable(o) for o in obj) elif isinstance(obj, dict): return tuple((k, ForcedHashable.hashable(v)) for k, v in obj.items()) ... def __init__(self, data): self.data = data def __eq__(self, other): return self.data == other.data def __hash__(self): return hash(self.hashable(self.data)) 来包装其键。

ForcedHashable

它适用于基本情况...

class MutableKeyDict(UserDict):
    def __setitem__(self, key, value):
        self.data[ForcedHashable(key)] = value

    def __getitem__(self, item):
        return self.data[ForcedHashable(item)]

    def __contains__(self, item):
        return ForcedHashable(item) in self.data

但是遇到嵌套对象的问题。

d = MutableKeyDict()

d[[1, 2, 3]] = 'a'

print([1, 2, 3] in d)  # True
print((1, 2, 3) in d)  # False

当然,递归源自该语句:

d = MutableKeyDict()

x = []
x.append(x)

d[x] = 'foo' # raises a 'RecursionError: maximum recursion depth exceeded'

我刚刚完成了if isinstance(obj, (list, tuple)): return tuple(ForcedHashable.hashable(o) for o in obj) 的修复工作,有点像memo所使用的修复程序,但是后来我意识到,即使我这样做了,该方法也会引发{{ 1}}。

copy.deepcopy

问题

我希望以上内容至少适用于内置类型。

会不会有一种聪明的方法来解决RecursionError?如果没有,是否存在将相等项(仅内置类型)关联到临时哈希的规范方法?我们欢迎其他方法。

1 个答案:

答案 0 :(得分:2)

没有理由使用deepcopy技术来解决递归问题。

我认为您可能会遗漏的是deepcopy的记忆是基于值id的。同样,您只需要捕获包含自己的对象,而不是包含相等但不同对象的对象。毕竟,您不可能拥有无穷但相等的物体的无限深度。这将需要无限的内存。

实际上,您可以使它比deepcopypickle更简单,因为对于重复的对象,返回的 并不重要,只要它是可散列且唯一。 1

例如,

def hashable(obj, *, memo=None):
    if memo is None:
        memo = set()
    if id(obj) in memo:
        return (..., id(obj))
    memo.add(id(obj))
    try:
        hash(obj)
        return obj
    except TypeError:
        if isinstance(obj, (list, tuple)):
            return tuple(ForcedHashable.hashable(o, memo=memo) for o in obj)
        elif isinstance(obj, set):
            return frozenset(ForcedHashable(o, memo=memo) for o in obj)
        elif isinstance(obj, dict):
            return frozenset((k, ForcedHashable.hashable(v, memo=memo)) for k, v in obj.items())
        raise

现在:

>>> x = []
>>> x.append(x)
>>> ForcedHashable.hashable(x)
((Ellipsis, 4658316360),)
>>> d = MutableKeyDict()
>>> d[x] = d
>>> d[x]
{<__main__.ForcedHashable object at 0x115855240>: 2, <__main__.ForcedHashable object at 0x115a247f0>: {...}}

在我们这样做的时候,请执行以下操作:

elif isinstance(obj, (dict, MutableKeyDict)):
    return frozenset((k, ForcedHashable.hashable(v, memo=memo)) for k, v in obj.items())

…现在:

>>> d = MutableKeyDict()
>>> d[d] = d
>>> d
{<__main__.ForcedHashable object at 0x11584b320>: {...}}

1。除非您希望它们像Quine原子一样工作,否则您希望它可以被散列并由相同类型的所有其他Quine原子共享,这同样容易。