两路/反向映射[重复]


101

我在python中做这个总机,需要跟踪谁在和谁说话,所以如果Alice-> Bob,那意味着Bob-> Alice。

是的,我可以填充两个哈希图,但是我想知道是否有人想使用一个哈希图。

或建议其他数据结构。

没有多个对话。假设这是一个客户服务呼叫中心,所以当爱丽丝拨入总机时,她只会和鲍勃交谈。他的答复也只发给她。


15
请注意,您正在描述双射图。
Nick Dandoulakis 09年

这是一个简单的双射词典实现,尽管我不知道它是否会满足您的性能要求。(此博客文章关于Boost Bimap for Python的链接,其中对该主题进行了一些不错的讨论。)
系统暂停

2
如果爱丽丝在跟鲍勃说话,我认为她也不能跟查尔斯说话。鲍勃也不能和其他人说话吗?另外,您在任何给定时间可以有多少人和多少对话?
系统暂停

不...不在我的总机上。爱丽丝发给我的任何消息都必须交给鲍勃。只是我将路由数千个同时进行的对话。但是每个人一次只能与另一个人交谈。
Sudhir Jonathan

1
不,我只需要将客户的消息路由给话务员,反之亦然……甚至不以任何方式存储对话。
Sudhir Jonathan

Answers:


90

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

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

它的工作原理如下:

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

我确定我没有涵盖所有情况,但这应该可以帮助您入门。


2
@SudhirJonathan:您可以在这个想法上走得更远-例如,添加一个.add方法,以便您可以执行类似的操作,d.add('Bob', 'Alice')而不使用我显示的语法。我还将包括一些错误处理。但是您了解了基本思想。:)
Sasha Chedygov

1
我猜这属于这些添加项,但是在设置新密钥对时删除旧密钥对会有所帮助(d['foo'] = 'baz'需要另外删除bar密钥)。
beardc 2013年

@TobiasKienzler:您是正确的,但这被列为问题中的假设。
Sasha Chedygov

5
同样值得一提的是:子类化dict在这里会产生一些误导性的行为,因为如果您创建具有一些初始内容的对象,结构就会被破坏。__init__需要被覆盖以使建筑d = TwoWayDict({'foo' : 'bar'})能够正常工作。
亨利·凯特

19
只是想提一下,这里有一个库:pip install bidict。网址:pypi.python.org/pypi/bidict
user1036719 2015年

45

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

relation = {}
relation['Alice'] = 'Bob'
relation['Bob'] = 'Alice'

由于您要描述的是对称关系。 A -> B => B -> A


3
嗯...是的,我最喜欢这个。试图避免输入两个条目,但这是迄今为止的最佳主意。
Sudhir Jonathan

1
仍然认为应该有两种路线图:-/
Sudhir Jonathan

如果必须高效,那么在幕后您需要将两个键都以某种索引数据结构进行索引-无论是散列,排序列表,二叉树,特里,充满sistring的后缀数组,还是什至是更具异国情调。在Python中执行此操作的简单方法是使用哈希。
Kragen Javier Sitaker,09年

@SudhirJonathan如果您更喜欢真正的双向地图,请查看本问题中提到的二分法 -但是请注意Aya我的重复问题评论中讨论的性能问题。
Tobias Kienzler 2013年

25

我知道这是一个比较老的问题,但是我想提到另一个解决此问题的好方法,即python软件包bidict。使用起来非常简单:

from bidict import bidict
map = bidict(Bob = "Alice")
print(map["Bob"])
print(map.inv["Alice"])

24

我只是用第二个哈希填充

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

7
在那儿得到了一些额外的括号:reverse_map = dict(reversed(item) for item in forward_map.items())
Andriy Drozdyuk 2011年

1
如果您不打算进一步更新字典,那么这是一种不错的简便方法。我用过my_dict.update(dict(reversed(item) for item in my_dict.items()))
Gilly

在Python 3中使用此代码时,我得到警告:Unexpected type(s): (Generator[Iterator[Union[str, Any]], Any, None]) Possible types: (Mapping) (Iterable[Tuple[Any, Any]])。任何想法如何摆脱警告?
Kerwin Sneijders

9

假设您可以节省内存,那么两个哈希映射实际上可能是性能最快的解决方案。我会将它们包装在一个类中-程序员的负担是确保两个哈希映射正确同步。


2
+1,这就是bidict的基本功能,外加用于通过mydict[:value]获取key(以牺牲一些性能为代价)访问逆映射的糖
Tobias Kienzler 2013年

6

您有两个单独的问题。

  1. 您有一个“对话”对象。它指的是两个人。由于一个人可以进行多个对话,因此您具有多对多关系。

  2. 您有一个从人到会话列表的映射。一个转换将有一对人。

做这样的事情

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 )

5

不,没有创建两个词典,实际上是没有办法做到的。在继续提供可比性能的同时,如何仅用一个字典就能实现呢?

最好创建一个自定义类型来封装两个字典,并提供所需的功能。


3

较不冗长的方式,仍然使用反转:

dict(map(reversed, my_dict.items()))


2

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

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. 

例:

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

2

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

使用bijection类非常简单:

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

2

我喜欢其中一项评论中关于二分法的建议。

pip install bidict

用途:

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

由于没有太多相关文档。但是我可以正常使用所有需要的功能。

印刷品:

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


1

这是通过扩展pythons dict类的另一种双向字典实现,以防您不喜欢其他任何字典:

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字典:

dd = DoubleD()
dd['foo'] = 'bar'

1

我喜欢做这种事情的一种方式是:

{my_dict[key]: key for key in my_dict.keys()}

1
欢迎来到StackOverflow!请编辑您的答案并为您的代码添加更多说明,最好还添加有关其与其他14个答案不同的描述。这个问题已有十多年的历史了,并且已经有了公认的答案,而且还有很多经充分解释和支持的答案。如果您的帖子中没有更多细节,则它的质量相对较低,可能会被否决或删除。添加额外的信息将有助于证明答案的存在。
Das_Geek
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.