Python中是否有'multimap'实现?

87

我是Python的新手,熟悉其他 语言Multimaps的实现。Python是否内置此类数据结构或在常用库中提供?

为了说明我的“multimap”意思:

a = multidict()
a[1] = 'a'
a[1] = 'b'
a[2] = 'c'

print(a[1])  # prints: ['a', 'b']
print(a[2])  # prints: ['c']

3
@ccfenix,我添加了一个我认为符合您要求的示例。如果不正确,请编辑以使示例正确。示例有助于人们回答您的问题;他们需要知道您正在寻找什么。 - steveha
是的,这正是我想要的,谢谢Steveha! - James Bond
http://code.activestate.com/recipes/576835-multimap-associating-multiple-values-to-a-key/ 似乎实现了你所需要的语法。 - Chozabu
1
在Python中,使用a[1] = 'b'表示向a[1]追加内容会让阅读或维护代码的人感到困惑。我建议你不要这样做。 - poolie
7个回答

146

标准库中没有这样的东西。 但是您可以使用defaultdict:

>>> from collections import defaultdict
>>> md = defaultdict(list)
>>> md[1].append('a')
>>> md[1].append('b')
>>> md[2].append('c')
>>> md[1]
['a', 'b']
>>> md[2]
['c']

你可以使用set替代list,这样你可以使用.add方法代替.append方法。


顺便提一下:看看你写的这两行代码:

a[1] = 'a'
a[1] = 'b'

根据这个问题,你似乎想要让表达式a[1]等于两个不同的值。但是由于字典的键是唯一的且每个键只与一个值相关联,所以这是不可能的。不过你可以提取与给定键关联的列表中的所有值,逐个进行操作。你可以使用iter函数,然后连续调用next来实现。或者你可以使用两个循环:

>>> for k, v in md.items():
...     for w in v:
...         print("md[%d] = '%s'" % (k, w))
... 
md[1] = 'a'
md[1] = 'b'
md[2] = 'c'

9

仅供未来访客参考。目前已有 Python 实现的 Multimap。可通过pypi 获取。


3
这个项目与其他项目的区别在于,由同一键映射的值不会被一起排序。这与使用defaultdict(set)有何不同? - Shuklaswag
1
你的链接上并没有写“ordered”,而是写着“grouped”。因此它不是一个集合,因为同一个元素可以被插入两次。我猜它更像是一个排序过的列表。 - Eyal
这是2011年的最新更新。它似乎是用Python 2编写的,并且做出了一些相当糟糕的设计决策。最糟糕的是:items()不会返回所有的项(每个唯一键只返回一个随机值),而且删除一个键基本上会重写整个字典,所以时间复杂度是O(n)。我认为这不是生产级别的代码。 - undefined

5
您可以取一个元组列表,然后像使用multimap一样对它们进行排序。
listAsMultimap=[]

让我们追加一些元素(元组):
listAsMultimap.append((1,'a'))
listAsMultimap.append((2,'c'))
listAsMultimap.append((3,'d'))
listAsMultimap.append((2,'b'))
listAsMultimap.append((5,'e'))
listAsMultimap.append((4,'d'))

现在对它进行排序。

listAsMultimap=sorted(listAsMultimap)

打印后,您将得到:

[(1, 'a'), (2, 'b'), (2, 'c'), (3, 'd'), (4, 'd'), (5, 'e')]

这意味着它正像Multimap一样工作!

请注意,就像Multimap一样,如果键相同,值也会按升序排序(对于key=2,'b'在'c'之前,尽管我们没有按此顺序追加它们。)

如果您想按降序获取它们,请将sorted()函数更改为以下内容:

listAsMultimap=sorted(listAsMultimap,reverse=True)

在完成后,您将得到类似以下的输出:

[(5, 'e'), (4, 'd'), (3, 'd'), (2, 'c'), (2, 'b'), (1, 'a')]

如果键相同,此处的值是按降序排列的。


3
这不同于multimap,multimap的成本为O(1),而上述内容的成本为O(NlogN)。 - user48956
@user48956,我猜你对unordered_multimap和multimap之间的区别感到困惑。Multimap的复杂度是O(NlogN),而不是O(1)。上面的实现是针对Multimap而不是使用哈希来降低时间复杂度并且平均具有恒定时间复杂度的unordered_multimap。在这里找到它们的区别:(https://en.cppreference.com/w/cpp/container/multimap)和(http://www.cplusplus.com/reference/unordered_map/unordered_multimap/)。 - hafiz031
2
在C++中,multimap查找的时间复杂度为O(logN)。 - Erik Aronesty

3

用Python编写这个的标准方法是使用一个字典,其元素分别为listset。正如stephan202所说,你可以使用defaultdict来自动化这一过程,但并非必须。

换句话说,我会将您的代码翻译为:

a = dict()
a[1] = ['a', 'b']
a[2] = ['c']

print(a[1])  # prints: ['a', 'b']
print(a[2])  # prints: ['c']

为什么会有踩票?因为它看起来不够像字典吗?我认为将a[1] = 'b'视为追加而不是替换a[1],这样做更令人困惑而不是有帮助。 - poolie
2
我没有点踩,但我认为你的建议没有为讨论增加任何内容,这可能解释了负面反应。你当然可以使用一个将键映射至值列表的字典,但这个手动实现很烦人、重复且有些容易出错。像 Java 的 Guava's Multimap 这样的 multimap 只不过是一个列表映射的映射,但它非常方便,因为它隐藏了实现细节。你的建议是正确的,但缺少方便性。 - dimo414
4
我理解您的意思是:与其他人不同,我想表达的观点是:使用multimap并不符合Pythonic。惯用的方式是使用一个字典包含多个集合。 - poolie
我喜欢关于Pythonic的讨论。作为Java和Perl的使用者,我怀疑@dimo414的担忧与在添加新键时实例化列表有关。答案可以在这方面扩展什么是Pythonic。 - Chris
我更详细地查看了defaultdict并喜欢它作为解决方案。我猜测downvotes的原因是您没有解决defaultdict为您创建列表的问题。 - Chris

3

Stephan202给出了正确的答案,使用defaultdict。但是如果你想要一个类似于C++ STL multimap接口但性能更差的东西,你可以这样做:

multimap = []
multimap.append( (3,'a') )
multimap.append( (2,'x') )
multimap.append( (3,'b') )
multimap.sort()

现在,当您遍历multimap时,您将获得与std::multimap中一样的键值对。不幸的是,这意味着您的循环代码将开始变得像C++一样丑陋。

def multimap_iter(multimap,minkey,maxkey=None):
  maxkey = minkey if (maxkey is None) else maxkey
  for k,v in multimap:
    if k<minkey: continue
    if k>maxkey: break
    yield k,v

# this will print 'a','b'
for k,v in multimap_iter(multimap,3,3):
  print v

总之,defaultdict非常酷,充分利用了Python的强大功能,你应该使用它。

3

或者继承 dict

class Multimap(dict):
    def __setitem__(self, key, value):
        if key not in self:
            dict.__setitem__(self, key, [value])  # call super method to avoid recursion
        else
            self[key].append(value)

3
不过,这个东西并不会像字典一样完全按照预期的行为。 - johncip

3
目前Python标准库中没有多重映射(multi-map)。
WebOb有一个MultiDict类,用于表示HTML表单值,并被一些Python Web框架使用,因此该实现经过了实战测试。
Werkzeug也有一个MultiDict类,原因相同。

WebOb的实现只是将(键,值)对存储在一个无序列表中,因此所有操作的时间复杂度都是O(n)。Werkzeug的实现使用了一个列表字典,因此相对而言更高效。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接