如何实现高效的双向哈希表?

问题描述 投票:56回答:6

Python dict是一个非常有用的数据结构:

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

d['a'] # get 1

有时你也想按值索引。

d[1] # get 'a'

哪种方法是实现此数据结构的最有效方法?有官方推荐的方法吗?

python hashtable bidirectional
6个回答
47
投票

这是一个双向dict的类,受Finding key from value in Python dictionary启发并修改为允许以下2)和3)。

注意 :

  • 1)当标准字典bd.inverse被修改时,逆目录bd自动更新。
  • 2)逆目录bd.inverse[value]总是key的列表,使bd[key] == value
  • 3)与bidicthttps://pypi.python.org/pypi/bidict模块不同,这里我们可以有2个具有相同值的键,这非常重要。

码:

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)

用法示例:

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']}

35
投票

您可以通过以相反顺序添加键,值对来使用相同的dict本身。

d={'a':1,'b':2}
revd=dict([reversed(i) for i in d.items()])
d.update(revd)

32
投票

穷人的双向哈希表只使用两个词典(这些词典已经是高度调整的数据结构)。

索引上还有一个bidict包:

bidict的来源可以在github上找到:


2
投票

下面的代码片段实现了一个可逆(双射)映射:

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)

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

>>> foo = BijectiveMap()
>>> foo['steve'] = 42
>>> foo.inverse
{42: 'steve'}
>>> foo.inverse.inverse
{'steve': 42}
>>> foo.inverse.inverse is foo
True

1
投票

这样的事情,也许:

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

如果多个键具有给定值,您必须决定要发生什么;你插入的一些后来的一对很容易破坏给定对的双向性。我实现了一个可能的选择。


示例:

bd = BidirDict({'a': 'myvalue1', 'b': 'myvalue2', 'c': 'myvalue2'})
print bd['myvalue1']   # a
print bd['myvalue2']   # b        

0
投票

首先,您必须确保值映射的键是一对一的,否则,无法构建双向映射。

第二,数据集有多大?如果没有太多数据,只需使用2个单独的地图,并在更新时更新它们。或者更好的是,使用像Bidict这样的现有解决方案,它只是2个dicts的包装,内置更新/删除。

但是如果数据集很大,并且不希望维持2个dicts:

  • 如果键和值都是数字,请考虑使用插值逼近映射的可能性。如果映射函数(及其映射函数)可以覆盖绝大多数键值对 反转功能),那么你只需要记录地图中的异常值。
  • 如果大多数访问是单向的(键 - >值),则可以逐步构建反向映射,以便交换时间 空间。

码:

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]
© www.soinside.com 2019 - 2024. All rights reserved.