87

I am new to Python, and I am familiar with implementations of Multimaps in other languages. Does Python have such a data structure built-in, or available in a commonly-used library?

To illustrate what I mean by "multimap":

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

print(a[1])  # prints: ['a', 'b']
print(a[2])  # prints: ['c']
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
James Bond
  • 7,533
  • 19
  • 50
  • 64
  • 3
    @ccfenix, I added an example of what I think you wanted. If this is wrong, please edit to make the example correct. An example helps people to answer your question; they need to know what you are looking for. – steveha Nov 13 '09 at 22:55
  • yah, exactly what i want, thank you steveha! – James Bond Nov 14 '09 at 07:40
  • http://code.activestate.com/recipes/576835-multimap-associating-multiple-values-to-a-key/ seems to implement the syntax you are after – Chozabu Feb 20 '14 at 18:50
  • 1
    Using `a[1] = 'b'` to mean *append to a[1]* is going to be confusing for people reading or maintaining this code. I would recommend you not do this in Python. – poolie Feb 03 '16 at 00:16

7 Answers7

146

Such a thing is not present in the standard library. You can use a defaultdict though:

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

(Instead of list you may want to use set, in which case you'd call .add instead of .append.)


As an aside: look at these two lines you wrote:

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

This seems to indicate that you want the expression a[1] to be equal to two distinct values. This is not possible with dictionaries because their keys are unique and each of them is associated with a single value. What you can do, however, is extract all values inside the list associated with a given key, one by one. You can use iter followed by successive calls to next for that. Or you can just use two loops:

>>> 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'
Stephan202
  • 59,965
  • 13
  • 127
  • 133
10

Just for future visitors. Currently there is a python implementation of Multimap. It's available via pypi

Michal
  • 6,411
  • 6
  • 32
  • 45
  • 2
    "The defining quality of this project vs others is that values mapped by the same key are not ordered together." How is this different than using `defaultdict(set)`? – Shuklaswag Jan 13 '18 at 18:24
  • It doesn't say "ordered" at your link, it says "grouped". So it's not a set because the same element can be inserted twice. It behaves more like a sorted list, I guess. – Eyal Dec 22 '19 at 15:07
6

You can take list of tuples and than can sort them as if it was a multimap.

listAsMultimap=[]

Let's append some elements (tuples):

listAsMultimap.append((1,'a'))
listAsMultimap.append((2,'c'))
listAsMultimap.append((3,'d'))
listAsMultimap.append((2,'b'))
listAsMultimap.append((5,'e'))
listAsMultimap.append((4,'d'))

Now sort it.

listAsMultimap=sorted(listAsMultimap)

After printing it you will get:

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

That means it is working as a Multimap!

Please note that like multimap here values are also sorted in ascending order if the keys are the same (for key=2, 'b' comes before 'c' although we didn't append them in this order.)

If you want to get them in descending order just change the sorted() function like this:

listAsMultimap=sorted(listAsMultimap,reverse=True)

And after you will get output like this:

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

Similarly here values are in descending order if the keys are the same.

hafiz031
  • 2,236
  • 3
  • 26
  • 48
  • 2
    This NOT the same as a multimap, where has O(1) costs. The above is O(NlogN). – user48956 May 24 '19 at 16:36
  • @user48956 , I guess you are being confused between unordered_multimap and multimap. Multimap has a complixity of O(NlogN) and NOT O(1). And above implementation is for Multimap and NOT for unordered_multimap which uses hashing to reduce time complexity and has a constant average time complexity on average. Find the differences here: (https://en.cppreference.com/w/cpp/container/multimap) and here: (http://www.cplusplus.com/reference/unordered_map/unordered_multimap/) – hafiz031 May 25 '19 at 12:47
  • 1
    in c++ multimap lookup is O(logN). – Erik Aronesty Mar 10 '21 at 16:26
4

Stephan202 has the right answer, use defaultdict. But if you want something with the interface of C++ STL multimap and much worse performance, you can do this:

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

Now when you iterate through multimap, you'll get pairs like you would in a std::multimap. Unfortunately, that means your loop code will start to look as ugly as 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

In summary, defaultdict is really cool and leverages the power of python and you should use it.

amwinter
  • 3,121
  • 2
  • 27
  • 25
3

Or subclass 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)
jay
  • 1,524
  • 4
  • 25
  • 45
  • 3
    This isn't going to behave exactly like a dict, though. http://stackoverflow.com/questions/3387691/python-how-to-perfectly-override-a-dict – johncip Apr 25 '14 at 00:40
3

The standard way to write this in Python is with a dict whose elements are each a list or set. As stephan202 says, you can somewhat automate this with a defaultdict, but you don't have to.

In other words I would translate your code to

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

print(a[1])  # prints: ['a', 'b']
print(a[2])  # prints: ['c']
Community
  • 1
  • 1
poolie
  • 9,289
  • 1
  • 47
  • 74
  • Why the downvotes? Because it doesn't look enough like a dict? I think treating `a[1] = 'b'` as appending to, rather than replacing, `a[1]`, is more confusing than helpful. – poolie Dec 07 '11 at 01:31
  • 2
    I didn't downvote, but I think your suggestion doesn't add anything to the discussion, which might explain the negative response. You certainly can use a dict of keys to lists of values, but this manual implementation is annoying, repetitive, and somewhat error-prone. A multimap, like [Guava's Multimap](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/ArrayListMultimap.html) for Java, is nothing more than a map of lists, but it's tremendously convenient exactly because it hides that implementation. Your suggestion is accurate, but it lacks any convenience. – dimo414 Feb 19 '13 at 06:52
  • 4
    The point I was trying to make, which is not made by the others, is: using a multimap is not Pythonic. The idiomatic way is a dict of sets. – poolie Aug 19 '14 at 22:54
  • I like the discussion of pythonic. Coming from Java and Perl I suspect @dimo414's concern has to do with instantiating the list when adding a new key. The answer could have expanded on the what is pythonic in that regard. – Chris Jul 19 '17 at 14:28
  • ...went and looked at defaultdict in more detail and like it as a solution. I would guess the downvotes are because you didn't address creating the list that defaultdict does for you. – Chris Jul 19 '17 at 14:43
2

There is no multi-map in the Python standard libs currently.

WebOb has a MultiDict class used to represent HTML form values, and it is used by a few Python Web frameworks, so the implementation is battle tested.

Werkzeug also has a MultiDict class, and for the same reason.

Luciano Ramalho
  • 1,981
  • 18
  • 22