关于python:双向/反向映射

Two way/reverse map

我在python中做这个交换台的工作,我需要跟踪谁在和谁说话,所以如果alice——>bob,那么这意味着bob——>alice。

是的,我可以填充两个散列图,但是我想知道是否有人想用一个散列图来填充。

或者建议其他数据结构。

没有多个对话。假设这是一个客户服务呼叫中心,所以当爱丽丝拨电话总机时,她只想和鲍勃通话。他的回答也只针对她。


您可以通过对dict进行子类化并添加所需的逻辑来创建自己的字典类型。下面是一个基本示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TwoWayDict(dict):
    def __setitem__(self, key, value):
        # Remove any previous connections with these values
        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):
        dict.__delitem__(self, self[key])
        dict.__delitem__(self, key)

    def __len__(self):
       """Returns the number of connections"""
        return dict.__len__(self) // 2

它的工作原理是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> d = TwoWayDict()
>>> d['foo'] = 'bar'
>>> d['foo']
'bar'
>>> d['bar']
'foo'
>>> len(d)
1
>>> del d['foo']
>>> d['bar']
Traceback (most recent call last):
  File"<stdin>", line 7, in <module>
KeyError: 'bar'

我肯定我没有把所有的案子都办妥,但这应该能让你开始。


在您的特殊情况下,您可以将这两个存储在一个字典中:

1
2
3
relation = {}
relation['Alice'] = 'Bob'
relation['Bob'] = 'Alice'

因为你所描述的是对称关系。A -> B => B -> A


我只需要填充第二个哈希,

1
reverse_map = dict((reversed(item) for item in forward_map.items()))


我知道这是一个古老的问题,但我想提一下这个问题的另一个很好的解决方案,即python包bidict。它的使用非常直接:

1
2
3
4
from bidict import bidict
map = bidict(Bob ="Alice")
print(map["Bob"])
print(map.inv["Alice"])

假设您可以节省内存,两个哈希图实际上可能是执行速度最快的解决方案。我将把它们包装在一个类中——程序员的负担是确保两个散列映射正确同步。


你有两个不同的问题。

  • 你有一个"对话"对象。指两个人。因为一个人可以有多个对话,所以你有多对多的关系。

  • 你有一张从人到对话列表的地图。转化会有一对人。

  • 像这样做

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from collections import defaultdict
    switchboard= defaultdict( list )

    x = Conversation("Alice","Bob" )
    y = Conversation("Alice","Charlie" )

    for c in ( x, y ):
        switchboard[c.p1].append( c )
        switchboard[c.p2].append( c )


    不,如果不创建两个字典,就真的没有办法做到这一点。在继续提供可比较的性能的同时,如何只使用一个字典来实现这一点呢?

    最好创建一个封装两个字典并公开所需功能的自定义类型。


    我喜欢其中一条评论中的投标建议。

    pip install 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
    # This normalization method should save hugely as aDaD ~ yXyX have the same form of smallest grammar.
    # To get back to your grammar's alphabet use trans

    def normalize_string(s, nv=None):
        if nv is None:
            nv = ord('a')
        trans = bidict()
        r = ''
        for c in s:
            if c not in trans.inverse:
                a = chr(nv)
                nv += 1
                trans[a] = c
            else:
                a = trans.inverse[c]
            r += a
        return r, trans


    def translate_string(s, trans):
        res = ''
        for c in s:
            res += trans[c]
        return res


    if __name__ =="__main__":
        s ="bnhnbiodfjos"

        n, tr = normalize_string(s)
        print(n)
        print(tr)
        print(translate_string(n, tr))

    因为没有太多的文件。但我已经拥有了它所需要的所有功能,可以正常工作。

    印刷品:

    1
    2
    3
    abcbadefghei
    bidict({'a': 'b', 'b': 'n', 'c': 'h', 'd': 'i', 'e': 'o', 'f': 'd', 'g': 'f', 'h': 'j', 'i': 's'})
    bnhnbiodfjos

    pypi上有collections扩展库:https://pypi.python.org/pypi/collections extended/0.6.0

    使用双射类非常简单:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    RESPONSE_TYPES = bijection({
        0x03 : 'module_info',
        0x09 : 'network_status_response',
        0x10 : 'trust_center_device_update'
    })
    >>> RESPONSE_TYPES[0x03]
    'module_info'
    >>> RESPONSE_TYPES.inverse['network_status_response']
    0x09

    另一种可能的解决方案是实现dict的子类,它保存原始字典并跟踪它的反向版本。如果键和值重叠,则保留两个单独的dict可能很有用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class TwoWayDict(dict):
        def __init__(self, my_dict):
            dict.__init__(self, my_dict)
            self.rev_dict = {v : k for k,v in my_dict.iteritems()}

        def __setitem__(self, key, value):
            dict.__setitem__(self, key, value)
            self.rev_dict.__setitem__(value, key)

        def pop(self, key):
            self.rev_dict.pop(self[key])
            dict.pop(self, key)

        # The above is just an idea other methods
        # should also be overridden.

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    >>> d = {'a' : 1, 'b' : 2} # suppose we need to use d and its reversed version
    >>> twd = TwoWayDict(d)    # create a two-way dict
    >>> twd
    {'a': 1, 'b': 2}
    >>> twd.rev_dict
    {1: 'a', 2: 'b'}
    >>> twd['a']
    1
    >>> twd.rev_dict[2]
    'b'
    >>> twd['c'] = 3    # we add to twd and reversed version also changes
    >>> twd
    {'a': 1, 'c': 3, 'b': 2}
    >>> twd.rev_dict
    {1: 'a', 2: 'b', 3: 'c'}
    >>> twd.pop('a')   # we pop elements from twd and reversed  version changes
    >>> twd
    {'c': 3, 'b': 2}
    >>> twd.rev_dict
    {2: 'b', 3: 'c'}

    您可以使用一个DoubleDict,如python食谱578224所示。


    下面是一个通过扩展pythons dict类实现的双向字典,以防您不喜欢其他类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class DoubleD(dict):
       """ Access and delete dictionary elements by key or value."""

        def __getitem__(self, key):
            if key not in self:
                inv_dict = {v:k for k,v in self.items()}
                return inv_dict[key]
            return dict.__getitem__(self, key)

        def __delitem__(self, key):
            if key not in self:
                inv_dict = {v:k for k,v in self.items()}
                dict.__delitem__(self, inv_dict[key])
            else:
                dict.__delitem__(self, key)

    将其用作普通的python字典,但在构造中除外:

    10


    KJBucketsC扩展模块提供了一个"图形"数据结构,我相信它能提供您想要的东西。