DEV Community

Cover image for How to Invert a Dictionary in Python: Comprehensions, Defaultdict, and More
Jeremy Grifski
Jeremy Grifski

Posted on • Edited on • Originally published at therenegadecoder.com

How to Invert a Dictionary in Python: Comprehensions, Defaultdict, and More

Article was originally posted on therenegadecoder.com where it's actively maintained.

In this series, I’m putting together several articles for small Python problems that can be solved in a few lines of code. This series is inspired by the everyday Google searches I make to solve my own problems at work. Today, I'm writing an article on how to invert a dictionary.

Problem Introduction

Recently, I was working on a Python project where I needed to invert a dictionary. For all you Boku no Hero Academia (BNHA) fans out there, I basically wanted to be able to do the following:

my_dict = {
  'Izuku Midoriya': 'One for All', 
  'Katsuki Bakugo': 'Explosion', 
  'All Might': 'One for All', 
  'Ochaco Uraraka': 'Zero Gravity'
}

my_inverted_dict = {
  'One for All': ['Izuku Midoriya', 'All Might'], 
  'Explosion': ['Katsuki Bakugo'], 
  'Ochaco Uraraka': ['Zero Gravity']
}
Enter fullscreen mode Exit fullscreen mode

In this scenario, we have a dictionary of characters from BNHA mapped to their quirks, and we want to convert that to a dictionary of quirks mapped to the characters that have them.

Unfortunately, the original dictionary has non-unique values, so flipping the dictionary would result in a loss of keys. Instead, we want to accumulate a list of keys for each non-unique value while performing the inversion. As it turns out, this is pretty easy to do.

Solutions

If we want to invert a dictionary, we have several options. From comprehensions to loops, we’ll take a look at all the practical solutions.

Invert a Dictionary with Map and Reversed

Let’s kick off our series of options with one of the more succinct options:

my_inverted_dict = dict(map(reversed, my_dict.items()))
Enter fullscreen mode Exit fullscreen mode

Here, we use the map function which applies the reversed function to all the items in the dictionary. Then, we take the map object and convert it into a dictionary. The result looks something like the following:

my_inverted_dict = {
  'One for All': 'All Might', 
  'Explosion': 'Katsuki Bakugo', 
  'Zero Gravity': 'Ochaco Uraraka'
}
Enter fullscreen mode Exit fullscreen mode

Of course, if we want to make sure we don’t lose Midoriya, we shouldn’t use this method. Otherwise, this would be a perfect solution.

Invert a Dictionary with a Comprehension

In Python 2.7 and above, we can use a dictionary comprehension to invert a dictionary. Unfortunately, it falls prey to the same issue mentioned before, but it gets the job done for unique values:

my_inverted_dict = {value: key for key, value in my_dict.items()}
Enter fullscreen mode Exit fullscreen mode

Once again, here’s the result of inverting our example dictionary:

my_inverted_dict = {
  'One for All': 'All Might', 
  'Explosion': 'Katsuki Bakugo', 
  'Zero Gravity': 'Ochaco Uraraka'
}
Enter fullscreen mode Exit fullscreen mode

As we can see, we lose one of our keys. So, there has to be a better way to invert a dictionary.

Invert a Dictionary with Defaultdict

Luckily, there is a better way! Thanks to our friend, Niels van Galen Last, we can accomplish exactly what we need in three lines of code:

from collections import defaultdict
my_inverted_dict = defaultdict(list)
{my_inverted_dict[v].append(k) for k, v in my_dict.items()}
Enter fullscreen mode Exit fullscreen mode

Here, we've chosen to leverage the defaultdict object from collections. It allows us to set default values for keys. In this case, we've chosen a default value of list, so we can append data without the risk of a runtime error. Check out the results:

my_inverted_dict = {
  'One for All': ['Izuku Midoriya', 'All Might'], 
  'Explosion': ['Katsuki Bakugo'], 
  'Zero Gravity': ['Ochaco Uraraka']
}
Enter fullscreen mode Exit fullscreen mode

Since defaultdict works like a regular dict, we can interact with it all the same. This is great solution if you don't have any strict dict requirements, and your values are not unique.

Invert a Dictionary with a For Loop

Another way to invert a dictionary is to use a for loop. This allows us to iterate over the set of mappings and properly build the new mappings by hand. Take a look:

my_inverted_dict = dict()
for key, value in my_dict.items():
    my_inverted_dict.setdefault(value, list()).append(key)
Enter fullscreen mode Exit fullscreen mode

With this method, we can invert a dictionary while preserving all of our original keys. Let’s take a look at what would happen if we ran this code snippet:

my_inverted_dict = {
  'One for All': ['Izuku Midoriya', 'All Might'], 
  'Explosion': ['Katsuki Bakugo'], 
  'Zero Gravity': ['Ochaco Uraraka']
}
Enter fullscreen mode Exit fullscreen mode

Great! We have exactly what we need, but what happens if we want to return this dictionary to its original form?

Revert the Inversion

In the basic case where all keys and values are unique, we can revert a dictionary back to its original mapping using the same dictionary comprehension we’ve already covered:

my_dict = {value: key for key, value in my_inverted_dict.items()}
Enter fullscreen mode Exit fullscreen mode

Unfortunately, this doesn’t work out with a dictionary that maps keys to lists. That’s because lists in Python are unhashable types. In other words, Python doesn’t allow lists to be keys in dictionaries because lists are not immutable.

Fortunately, it’s easier to revert our dictionary than it was to invert it in the first place. We can use the following dictionary comprehension:

my_dict = {value: key for key in my_inverted_dict for value in my_map[key]}
Enter fullscreen mode Exit fullscreen mode

As we can see, we make a new key-value pair for every single value in each list using this double loop structure.

A Little Recap

Using the methods above, we can invert just about any dictionary.

# Use to invert dictionaries that have unique values
my_inverted_dict = dict(map(reversed, my_dict.items()))

# Use to invert dictionaries that have unique values
my_inverted_dict = {value: key for key, value in my_dict.items()}

# Use to invert dictionaries that have non-unique values
from collections import defaultdict
my_inverted_dict = defaultdict(list)
{my_inverted_dict[v].append(k) for k, v in my_dict.items()}

# Use to invert dictionaries that have non-unique values
my_inverted_dict = dict()
    for key, value in my_dict.items():
        my_inverted_dict.setdefault(value, list()).append(key)

# Use to invert dictionaries that have lists of values
my_dict = {value: key for key in my_inverted_dict for value in my_map[key]}
Enter fullscreen mode Exit fullscreen mode

Just about every other type of dictionary transformation is out of the scope of this tutorial. However, if you have any specific questions, consider dropping them down below in the comments. I'm always happy to write an article for someone!

Top comments (7)

Collapse
 
mikedh profile image
Michael Dawson-Haggerty

You can also use zip if you don't mind non-unique values being merged:

In [11]: d = {'hi': 'stuff'}

In [12]: dict(zip(d.values(), d.keys()))
Out[12]: {'stuff': 'hi'}
Collapse
 
renegadecoder94 profile image
Jeremy Grifski

Good point! I always hesitate with this solution because it’s not immediately clear that the two collections would maintain their order. I know they do, but it still bothers me.

Collapse
 
mikedh profile image
Michael Dawson-Haggerty

Yeah, the dict comprehension is the clearest way of doing this. zip is mildly faster but I agree calling keys and values separately is slightly offputting:

In [7]: d = {a: b for a,b in (np.random.random((100000, 2)) * 1e6).astype(int)}

In [8]: %timeit {v: k for k, v in d.items()}
100 loops, best of 3: 12.3 ms per loop

In [9]: %timeit dict(zip(d.values(), d.keys()))
100 loops, best of 3: 10.8 ms per loop

Collapse
 
nielsgl profile image
Niels van Galen Last • Edited

Inverting the original dict with non-unique values can be done cleaner (without the explicit two line for loop and IMO easier to read) using a defaultdict:

>>> from collections import defaultdict
>>> # Original dict
>>> my_dict = {
  'Izuku Midoriya': 'One for All',
  'Katsuki Bakugo': 'Explosion',
  'All Might': 'One for All',
  'Ochaco Uraraka': 'Zero Gravity'
}
>>> my_dict_inverted = defaultdict(list)

>>> [my_dict_inverted[v].append(k) for k, v in my_dict.items()]

>>> my_dict_inverted
defaultdict(<class 'list'>, {'One for All': ['Izuku Midoriya', 'All Might'],
   'Explosion': ['Katsuki Bakugo'], 'Zero Gravity': ['Ochaco Uraraka']})

Or to make it more intuitive denote the comprehension using a dict since that is what we are applying the operation to:

>>> my_dict_inverted = defaultdict(list)

>>> {my_dict_inverted[v].append(k) for k, v in my_dict.items()}

>>> my_dict_inverted
defaultdict(<class 'list'>, {'One for All': ['Izuku Midoriya', 'All Might'],
   'Explosion': ['Katsuki Bakugo'], 'Zero Gravity': ['Ochaco Uraraka']})
Collapse
 
renegadecoder94 profile image
Jeremy Grifski • Edited

Great addition! I would prefer to maintain the dict type and leave out any extraneous imports, but this gets the job done and is probably less error prone.

Mind if I add it to the list?

Collapse
 
nielsgl profile image
Niels van Galen Last

Go ahead :)

It's part of the standard library and it's still a dict so you can use any it like a normal dict, e.g. my_dict_inverted['One for All'] as well as all methods from a normal dict like keys(), values() and items().

Thread Thread
 
renegadecoder94 profile image
Jeremy Grifski

Hi again! I'm in the process of updating this article, and I just realized that this answer has a minor problem. We're using a dictionary comprehension in a bad way. We're updating some external dictionary while the comprehension will also generate a dictionary (in this case, {None}). I believe that's considered bad practice, so I've added a note in the original article.