关于python:如何实现高效的双向哈希表?

How to implement an efficient bidirectional hash table?

本问题已经有最佳答案,请猛点这里访问。

复制这个网站码到您的网站上以设置一个投票箱在您的网站上。

1
2
3
d = {'a': 1, 'b': 2}

d['a'] # get 1

有时候你也喜欢按价值指数

ZZU1

实现这一数据结构的最有效方式是什么?任何官方建议如何做?


这里是一个双向dict的类,其灵感来自于在python字典中从值中查找键,并修改为允许以下2)和3)。

注意:

  • 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)

这种实现的优点是,BijectiveMapinverse属性又是BijectiveMap属性。因此,您可以执行以下操作:

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]