How to implement an efficient bidirectional hash table?
本问题已经有最佳答案,请猛点这里访问。
复制这个网站码到您的网站上以设置一个投票箱在您的网站上。
1 2 3 | d = {'a': 1, 'b': 2} d['a'] # get 1 |
有时候你也喜欢按价值指数
ZZU1
实现这一数据结构的最有效方式是什么?任何官方建议如何做?
这里是一个双向
注意:
- 1)修改标准dict
bd 时,反向目录bd.inverse 自动更新。 - 2)反向目录
bd.inverse[value] 总是一个key 的列表,这样bd[key] == value 就可以了。 - 3)与https://pypi.python.org/pypi/bidict中的
bidict 模块不同,这里可以有两个具有相同值的键,这非常重要。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class bidict(dict): def __init__(self, *args, **kwargs): super(bidict, self).__init__(*args, **kwargs) self.inverse = {} for key, value in self.items(): self.inverse.setdefault(value,[]).append(key) def __setitem__(self, key, value): if key in self: self.inverse[self[key]].remove(key) super(bidict, self).__setitem__(key, value) self.inverse.setdefault(value,[]).append(key) def __delitem__(self, key): self.inverse.setdefault(self[key],[]).remove(key) if self[key] in self.inverse and not self.inverse[self[key]]: del self.inverse[self[key]] super(bidict, self).__delitem__(key) |
使用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | bd = bidict({'a': 1, 'b': 2}) print(bd) # {'a': 1, 'b': 2} print(bd.inverse) # {1: ['a'], 2: ['b']} bd['c'] = 1 # Now two keys have the same value (= 1) print(bd) # {'a': 1, 'c': 1, 'b': 2} print(bd.inverse) # {1: ['a', 'c'], 2: ['b']} del bd['c'] print(bd) # {'a': 1, 'b': 2} print(bd.inverse) # {1: ['a'], 2: ['b']} del bd['a'] print(bd) # {'b': 2} print(bd.inverse) # {2: ['b']} bd['b'] = 3 print(bd) # {'b': 3} print(bd.inverse) # {2: [], 3: ['b']} |
您可以通过按相反顺序添加键、值对来使用相同的dict本身。
1 2 3 | d={'a':1,'b':2} revd=dict([reversed(i) for i in d.items()]) d.update(revd) |
穷人的双向哈希表只需要使用两个字典(这些字典已经是高度优化的数据结构)。
索引上还有一个招标文件包:
- https://pypi.python.org/pypi/bidict
Bidict的来源可在Github上找到:
- https://github.com/jab/bidict
下面的代码片段实现了可逆(双射)映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class BijectionError(Exception): """Must set a unique value in a BijectiveMap.""" def __init__(self, value): self.value = value msg = 'The value"{}" is already in the mapping.' super().__init__(msg.format(value)) class BijectiveMap(dict): """Invertible map.""" def __init__(self, inverse=None): if inverse is None: inverse = self.__class__(inverse=self) self.inverse = inverse def __setitem__(self, key, value): if value in self.inverse: raise BijectionError(value) self.inverse._set_item(value, key) self._set_item(key, value) def __delitem__(self, key): self.inverse._del_item(self[key]) self._del_item(key) def _del_item(self, key): super().__delitem__(key) def _set_item(self, key, value): super().__setitem__(key, value) |
这种实现的优点是,
1 2 3 4 5 6 7 8 | >>> foo = BijectiveMap() >>> foo['steve'] = 42 >>> foo.inverse {42: 'steve'} >>> foo.inverse.inverse {'steve': 42} >>> foo.inverse.inverse is foo True |
可能是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import itertools class BidirDict(dict): def __init__(self, iterable=(), **kwargs): self.update(iterable, **kwargs) def update(self, iterable=(), **kwargs): if hasattr(iterable, 'iteritems'): iterable = iterable.iteritems() for (key, value) in itertools.chain(iterable, kwargs.iteritems()): self[key] = value def __setitem__(self, key, value): if key in self: del self[key] if value in self: del self[value] dict.__setitem__(self, key, value) dict.__setitem__(self, value, key) def __delitem__(self, key): value = self[key] dict.__delitem__(self, key) dict.__delitem__(self, value) def __repr__(self): return '%s(%s)' % (type(self).__name__, dict.__repr__(self)) |
如果不止一个键有一个给定的值,您必须决定要发生什么;给定对的双向性很容易被您插入的稍后对击倒。我实现了一个可能的选择。
例子:
1 2 3 | bd = BidirDict({'a': 'myvalue1', 'b': 'myvalue2', 'c': 'myvalue2'}) print bd['myvalue1'] # a print bd['myvalue2'] # b |
首先,必须确保键到值的映射是一对一的,否则,就不可能构建双向映射。
第二,数据集有多大?如果没有太多数据,只需使用两个单独的地图,并在更新时同时更新这两个地图。或者更好的做法是,使用现有的解决方案,比如bidict,它只是2个dict的包装器,内置了更新/删除功能。
但是,如果数据集很大,并且不希望维护两个dict:
如果键和值都是数字,请考虑使用插值以近似映射。如果绝大多数映射函数(及其反向函数),那么您只需要在映射中记录异常值。
如果大多数访问是单向的(key->value),那么它是完全的可以逐步构建反向映射,以交换时间空间。
代码:
1 2 3 4 5 6 7 8 9 10 | d = {1:"one", 2:"two" } reverse = {} def get_key_by_value(v): if v not in reverse: for _k, _v in d.items(): if _v == v: reverse[_v] = _k break return reverse[v] |