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

Jua*_*nti 65 python hashtable bidirectional

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

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

d['a'] # get 1
Run Code Online (Sandbox Code Playgroud)

有时你也想按值索引.

d[1] # get 'a'
Run Code Online (Sandbox Code Playgroud)

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

Bas*_*asj 51

这是一个双向类dict,受Python字典中的值Finding键的启发,并修改为允许以下2)和3).

注意 :

  • 1)当修改标准字典时,反向目录 bd.inverse自动更新bd.
  • 2)逆目录 bd.inverse[value]始终是一个列表key,使得bd[key] == value.
  • 3)与https://pypi.python.org/pypi/bidict中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)
Run Code Online (Sandbox Code Playgroud)

用法示例:

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']}
Run Code Online (Sandbox Code Playgroud)

  • **这是惊人的.**它很简洁; 它是自我记录的; 它的效率相当高; 它只是工作.我唯一的狡辩是优化`__delitem __()`中`self [key]`的重复查找,使用单个`value = self [key]`赋值给这些查找重用.但是...... _yeah._那可以忽略不计.感谢纯粹的真棒,[Basj](/sf/users/99546751/)! (5认同)
  • 非常巧妙的解决了暧昧的案例! (2认同)
  • 我认为这种数据结构在许多实际问题中非常有用. (2认同)
  • 啊。正确的。尝试不使用“iter”,它应该可以工作。 (2认同)

Emi*_*mil 36

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

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

  • 注意这个失败,例如`d = {'a':1,'b':2,1,'b'}` (18认同)
  • **在原始字典中添加反向映射是一个很糟糕的想法.**正如上面的评论所示,这样做在一般情况下是不安全的.只需保留两个单独的词典.因为这个答案的前两行忽略了尾随的'd.update(revd)`然而,我仍然在考虑一个upvote._让我们思考一下._ (13认同)
  • +1一个不错的实用解决方案.写它的另一种方法:`d.update(d((d [k],k)for d in d))`. (4认同)
  • +1用于反向()的巧妙使用.如果它比d.items()中的(k,v)显式`dict((v,k)更具可读性,那我就不确定了.在任何情况下,你可以直接将对传递给.update:`d.update(在d.items()中反转(i)for i). (4认同)
  • 轻微修改:`dict(map(reverse,a_dict.items()))`. (3认同)
  • 对于d = {'a':1,'b':2,'c':1}`失败。 (2认同)
  • 在上述情况下失败的事实与实现无关。隐含的是,在所描述的双向结构中,不能有重复的值/键(因为它们以相同的方式访问)。保留两个字典(任何形式)对应于不具有重复值或重复键的限制(但键和值中可以具有相同的值)。在这种情况下 `={'a':1, 'b':2, 1: 'b'}` 成功,而 `d={'a':1,'b':2, 'c': 1} ` 仍然失败,因为不是实现的问题。 (2认同)

mik*_*iku 32

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

索引上还有一个bidict包:

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

  • @Juanjo:几乎任何双向/可逆哈希表都将涉及"双重插入和删除",作为实现结构的一部分,或作为使用它的一部分.保持两个索引真的是唯一快速的方法,AFAIK. (12认同)
  • 当然; 我的意思是手工处理2指数就是问题所在. (7认同)
  • @Basj我认为它不被接受是正确的,因为具有多个值意味着它不再是双射并且对于反向查找来说是不明确的。 (2认同)

jme*_*jme 5

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

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)
Run Code Online (Sandbox Code Playgroud)

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

>>> foo = BijectiveMap()
>>> foo['steve'] = 42
>>> foo.inverse
{42: 'steve'}
>>> foo.inverse.inverse
{'steve': 42}
>>> foo.inverse.inverse is foo
True
Run Code Online (Sandbox Code Playgroud)