Brian M. Hunt
Brian M. Hunt

Reputation: 83828

Reverse / invert a dictionary mapping

Given a dictionary like so:

my_map = {'a': 1, 'b': 2}

How can one invert this map to get:

inv_map = {1: 'a', 2: 'b'}

Upvotes: 1088

Views: 911029

Answers (30)

Raymond Hettinger
Raymond Hettinger

Reputation: 226624

Simple Case -- Bijections (no duplicate values)

The solution is to loop over the start dictionary keys and value, and add them in reverse order to a new dictionary.

Example of reversing an english-to-spanish dictionary:

e2s = {'one': 'uno', 'two': 'dos', 'three': 'tres'}

Plain old-fashioned clear Python code:

s2e = {}
for english, spanish in e2s.items():
    s2e[spanish] = english

Dict comprehension:

>>> {spanish: english for english, spanish in e2s.items()}
{'uno': 'one', 'dos': 'two', 'tres': 'three'}

Functional approach:

>>> dict(map(reversed, s2e.items()))
{'one': 'uno', 'two': 'dos', 'three': 'tres'}

Functional approach with the operator module:

>>> dict(map(itemgetter(1, 0), e2s.items()))
{'uno': 'one', 'dos': 'two', 'tres': 'three'}

Many-to-many relationships

A dictionary of lists can model a one-to-many relationships.

boy_knows_girl = {'al': ['amy', 'sue'], 'bo': ['sue', 'dee'],
                  'kip': ['daisy', 'dee'], 'mark': ['amy', 'daisy']}

To model many-to-many relationships, the forward one-to-many dictionary should be reversed into a second one-to-many dictionary.

girl_knows_boy = {'amy': ['al', 'mark'], 'sue': ['al', 'bo'],
                  'dee': ['bo', 'kip'], 'daisy': ['kip', 'mark']}

Plain old-fashioned clear Python code:


girl_knows_boy = {}
for boy, girls in boy_knows_girl.items():
    for girl in girls:
        if girl not in girl_knows_boy:
            girl_knows_boy[girl] = []
        girl_knows_boy[girl].append(boy)

Simplify with dict.setdefault:


girl_knows_boy = {}
for boy, girls in boy_knows_girl.items():
    for girl in girls:
        girl_knows_boy.setdefault(girl, []).append(boy)

Simplify more with collections.defaultdict:


from collections import defaultdict

girl_knows_boy = defaultdict(list)
for boy, girls in boy_knows_girl.items():
    for girl in girls:
        girl_knows_boy[girl].append(boy)

More complex multiway relationships

Relationship tuples with multiple fields are modeled as a list of tuples:


data = [('bob', 'male', 'sorbonne', 35),
        ('amy', 'female', 'oxford', 30),
        ('sue', 'female', 'sapienza', 25),
        ('mark', 'male', 'oxford', 35),
        ('bo', 'male', 'harvard', 25),
        ('dee', 'female', 'sorbonne', 30),
        ('daisy', 'female', 'stanford', 25),
        ('al', 'male', 'stanford', 40),]

Unsurprisingly, the preferred tool designed for querying this data is a relational database such as sqlite3.


import sqlite3

conn = sqlite3.connect(':memory:')
conn.execute('CREATE TABLE Cohort (name text, gender text, school text, age integer)')
conn.executemany('INSERT INTO Cohort VALUES (?, ?, ?, ?)', data)

Once loaded, forward and reverse lookups between any of the fields are implemented with simple queries:


conn.execute('SELECT name, age FROM Cohort WHERE gender="male"').fetchall()
conn.execute('SELECT gender, age FROM Cohort WHERE school="stanford"').fetchall()
conn.execute('SELECT school, gender FROM Cohort WHERE age=30').fetchall()

Those queries can be made to run fast if the essential fields are in an index:


conn.execute('CREATE INDEX FastIndex ON Cohort ("gender", "school", "age")')

Upvotes: 0

Vasin Yuriy
Vasin Yuriy

Reputation: 535

This is a pretty popular question with two pages of different answers. So I decided to profile different approaches.

Here the most popular solutions:

def swap_loop(mock_dict):
    return dict((v,k) for k,v in mock_dict.items()) 

def swap_map(mock_dict):
    return dict(map(reversed, mock_dict.items())) 

def swap_map_lambda(mock_dict):
    return dict(map(lambda x: x[::-1], mock_dict.items())) 

def swap_slice(mock_dict):
    return {mock_dict[i]:i for i in mock_dict}

def swap_zip(mock_dict):
    return dict(zip(mock_dict.values(), mock_dict.keys()))

And results of profiling:

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1       0.128    0.128    0.247    0.247 script.py:34(swap_loop)
1       0.019    0.019    0.019    0.019 script.py:37(swap_map)
1       0.130    0.130    0.256    0.256 script.py:40(swap_map_lambda)
1       0.005    0.005    0.005    0.005 script.py:43(swap_slice)
1       0.003    0.003    0.003    0.003 script.py:46(swap_zip)

As you can see zip approach is the fastest, but in any way result contains only unique keys (result has less length).

Full code and the results here

Upvotes: 0

EmmaRenauld
EmmaRenauld

Reputation: 96

Depending on the use case, there is possibly a way to use enum:

import enum

class Reverse(enum.Enum):
    a = 1
    b = 2

You can access the values in both direction:

Reverse.a       --> prints Reverse.a
Reverse(1)      --> prints Reverse.a

Reverse.a.value --> prints 1
Reverse.a.name  --> prints 'a'

If 'a' is not known by the developper but rather contained in a variable my_var = 'a', the equivalent of my_dict[my_var] would be:

getattr(Reverse, my_var) --> prints Reverse.a

Upvotes: 0

Asclepius
Asclepius

Reputation: 63514

This expands upon the answer by Robert, applying to when the values in the dict aren't unique.

class ReversibleDict(dict):
    # Ref: https://stackoverflow.com/a/13057382/
    def reversed(self):
        """
        Return a reversed dict, with common values in the original dict
        grouped into a list in the returned dict.

        Example:
        >>> d = ReversibleDict({'a': 3, 'c': 2, 'b': 2, 'e': 3, 'd': 1, 'f': 2})
        >>> d.reversed()
        {1: ['d'], 2: ['c', 'b', 'f'], 3: ['a', 'e']}
        """
        
        revdict = {}
        for k, v in self.items():
            revdict.setdefault(v, []).append(k)
        return revdict

The implementation is limited in that you cannot use reversed twice and get the original back. It is not symmetric as such. It is tested with Python 2.6. Here is a use case of how I am using to print the resultant dict.

If you'd rather use a set than a list, and there could exist unordered applications for which this makes sense, instead of setdefault(v, []).append(k), use setdefault(v, set()).add(k).

Upvotes: 17

Andy Jazz
Andy Jazz

Reputation: 58553

For instance, you have the following dictionary:

my_dict = {'a': 'fire', 'b': 'ice', 'c': 'fire', 'd': 'water'}

And you wanna get it in such an inverted form:

inverted_dict = {'fire': ['a', 'c'], 'ice': ['b'], 'water': ['d']}

First Solution. For inverting key-value pairs in your dictionary use a for-loop approach:

# Use this code to invert dictionaries that have non-unique values

inverted_dict = dict()
for key, value in my_dict.items():
    inverted_dict.setdefault(value, list()).append(key)

Second Solution. Use a dictionary comprehension approach for inversion:

# Use this code to invert dictionaries that have unique values

inverted_dict = {value: key for key, value in my_dict.items()}

Third Solution. Use reverting the inversion approach (relies on the second solution):

# Use this code to invert dictionaries that have lists of values

my_dict = {value: key for key in inverted_dict for value in my_map[key]}

Upvotes: 13

user42
user42

Reputation: 959

This works even if you have non-unique values in the original dictionary.

def dict_invert(d):
    '''
    d: dict
    Returns an inverted dictionary 
    '''
    # Your code here
    inv_d = {}
    for k, v in d.items():
        if v not in inv_d.keys():
            inv_d[v] = [k]
        else:
            inv_d[v].append(k)
        inv_d[v].sort()
        print(f"{inv_d[v]} are the values")
        
    return inv_d

Upvotes: 0

questionto42
questionto42

Reputation: 9590

Taking up the highly voted answer starting If the values in my_map aren't unique:, I had a problem where not only the values were not unique, but in addition, they were a list, with each item in the list consisting again of a list of three elements: a string value, a number, and another number.

Example:

mymap['key1'] gives you:

[('xyz', 1, 2),
 ('abc', 5, 4)]

I wanted to switch only the string value with the key, keeping the two number elements at the same place. You simply need another nested for loop then:

inv_map = {}
for k, v in my_map.items():
    for x in v:
        # with x[1:3] same as x[1], x[2]:
        inv_map[x[0]] = inv_map.get(x[0], []) + [k, x[1:3]]

Example:

inv_map['abc'] now gives you:

[('key1', 1, 2),
 ('key1', 5, 4)]

Upvotes: 0

unbeknown
unbeknown

Reputation:

Assuming that the values in the dict are unique:

Python 3:

dict((v, k) for k, v in my_map.items())

Python 2:

dict((v, k) for k, v in my_map.iteritems())

Upvotes: 235

Robert Rossney
Robert Rossney

Reputation: 96860

If the values in my_map aren't unique:

Python 3:

inv_map = {}
for k, v in my_map.items():
    inv_map[v] = inv_map.get(v, []) + [k]

Python 2:

inv_map = {}
for k, v in my_map.iteritems():
    inv_map[v] = inv_map.get(v, []) + [k]

Upvotes: 212

algorytmus
algorytmus

Reputation: 973

dict([(value, key) for key, value in d.items()])

Upvotes: 1

Ani Menon
Ani Menon

Reputation: 28257

Lot of answers but didn't find anything clean in case we are talking about a dictionary with non-unique values.

A solution would be:

from collections import defaultdict

inv_map = defaultdict(list) 
for k, v in my_map.items(): 
    inv_map[v].append(k)

Example:

If initial dict my_map = {'c': 1, 'd': 5, 'a': 5, 'b': 10}

then, running the code above will give:

{5: ['a', 'd'], 1: ['c'], 10: ['b']}

Upvotes: 12

mss
mss

Reputation: 215

I am aware that this question already has many good answers, but I wanted to share this very neat solution that also takes care of duplicate values:

def dict_reverser(d):
    seen = set()
    return {v: k for k, v in d.items() if v not in seen or seen.add(v)}

This relies on the fact that set.add always returns None in Python.

Upvotes: 2

Mondaa
Mondaa

Reputation: 127

A case where the dictionary values is a set. Like:

some_dict = {"1":{"a","b","c"},
        "2":{"d","e","f"},
        "3":{"g","h","i"}}

The inverse would like:

some_dict = {vi: k  for k, v in some_dict.items() for vi in v}

The output is like this:

{'c': '1',
 'b': '1',
 'a': '1',
 'f': '2',
 'd': '2',
 'e': '2',
 'g': '3',
 'h': '3',
 'i': '3'}

Upvotes: 11

user8917421
user8917421

Reputation:

Here is another way to do it.

my_map = {'a': 1, 'b': 2}

inv_map= {}
for key in my_map.keys() :
    val = my_map[key]
    inv_map[val] = key

Upvotes: 1

SilentGhost
SilentGhost

Reputation: 319889

Python 3+:

inv_map = {v: k for k, v in my_map.items()}

Python 2:

inv_map = {v: k for k, v in my_map.iteritems()}

Upvotes: 1521

nauer
nauer

Reputation: 700

I found that this version is more than 10% faster than the accepted version of a dictionary with 10000 keys.

d = {i: str(i) for i in range(10000)}

new_d = dict(zip(d.values(), d.keys()))

Upvotes: 3

mit
mit

Reputation: 11271

A lambda solution for current python 3.x versions:

d1 = dict(alice='apples', bob='bananas')
d2 = dict(map(lambda key: (d1[key], key), d1.keys()))
print(d2)

Result:

{'apples': 'alice', 'bananas': 'bob'}

This solution does not check for duplicates.

Some remarks:

  • The lambda construct can access d1 from the outer scope, so we only pass in the current key. It returns a tuple.
  • The dict() constructor accepts a list of tuples. It also accepts the result of a map, so we can skip the conversion to a list.
  • This solution has no explicit for loop. It also avoids using a list comprehension for those who are bad at math ;-)

Upvotes: 0

irudyak
irudyak

Reputation: 2341

We can also reverse a dictionary with duplicate keys using defaultdict:

from collections import Counter, defaultdict

def invert_dict(d):
    d_inv = defaultdict(list)
    for k, v in d.items():
        d_inv[v].append(k)
    return d_inv

text = 'aaa bbb ccc ddd aaa bbb ccc aaa' 
c = Counter(text.split()) # Counter({'aaa': 3, 'bbb': 2, 'ccc': 2, 'ddd': 1})
dict(invert_dict(c)) # {1: ['ddd'], 2: ['bbb', 'ccc'], 3: ['aaa']}  

See here:

This technique is simpler and faster than an equivalent technique using dict.setdefault().

Upvotes: 21

Ersatz Kwisatz
Ersatz Kwisatz

Reputation: 57

This handles non-unique values and retains much of the look of the unique case.

inv_map = {v:[k for k in my_map if my_map[k] == v] for v in my_map.itervalues()}

For Python 3.x, replace itervalues with values.

Upvotes: 2

fs.
fs.

Reputation: 928

To do this while preserving the type of your mapping (assuming that it is a dict or a dict subclass):

def inverse_mapping(f):
    return f.__class__(map(reversed, f.items()))

Upvotes: 56

SVJ
SVJ

Reputation: 135

Combination of list and dictionary comprehension. Can handle duplicate keys

{v:[i for i in d.keys() if d[i] == v ] for k,v in d.items()}

Upvotes: 11

RVR
RVR

Reputation: 671

def invertDictionary(d):
    myDict = {}
  for i in d:
     value = d.get(i)
     myDict.setdefault(value,[]).append(i)   
 return myDict
 print invertDictionary({'a':1, 'b':2, 'c':3 , 'd' : 1})

This will provide output as : {1: ['a', 'd'], 2: ['b'], 3: ['c']}

Upvotes: 0

genghiscrade
genghiscrade

Reputation: 7

I would do it that way in python 2.

inv_map = {my_map[x] : x for x in my_map}

Upvotes: -1

mveith
mveith

Reputation: 1

If values aren't unique AND may be a hash (one dimension):

for k, v in myDict.items():
    if len(v) > 1:
        for item in v:
            invDict[item] = invDict.get(item, [])
            invDict[item].append(k)
    else:
        invDict[v] = invDict.get(v, [])
        invDict[v].append(k)

And with a recursion if you need to dig deeper then just one dimension:

def digList(lst):
    temp = []
    for item in lst:
        if type(item) is list:
            temp.append(digList(item))
        else:
            temp.append(item)
    return set(temp)

for k, v in myDict.items():
    if type(v) is list:
        items = digList(v)
        for item in items:
            invDict[item] = invDict.get(item, [])
            invDict[item].append(k)
    else:
        invDict[v] = invDict.get(v, [])
        invDict[v].append(k)

Upvotes: -2

Brendan Maguire
Brendan Maguire

Reputation: 4551

Another, more functional, way:

my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))

Upvotes: 45

dhvlnyk
dhvlnyk

Reputation: 307

Try this for python 2.7/3.x

inv_map={};
for i in my_map:
    inv_map[my_map[i]]=i    
print inv_map

Upvotes: 0

pcv
pcv

Reputation: 2181

If the values aren't unique, and you're a little hardcore:

inv_map = dict(
    (v, [k for (k, xx) in filter(lambda (key, value): value == v, my_map.items())]) 
    for v in set(my_map.values())
)

Especially for a large dict, note that this solution is far less efficient than the answer Python reverse / invert a mapping because it loops over items() multiple times.

Upvotes: 2

sykloid
sykloid

Reputation: 101356

Try this:

inv_map = dict(zip(my_map.values(), my_map.keys()))

(Note that the Python docs on dictionary views explicitly guarantee that .keys() and .values() have their elements in the same order, which allows the approach above to work.)

Alternatively:

inv_map = dict((my_map[k], k) for k in my_map)

or using python 3.0's dict comprehensions

inv_map = {my_map[k] : k for k in my_map}

Upvotes: 52

Taras Voitovych
Taras Voitovych

Reputation: 7

I wrote this with the help of cycle 'for' and method '.get()' and I changed the name 'map' of the dictionary to 'map1' because 'map' is a function.

def dict_invert(map1):
    inv_map = {} # new dictionary
    for key in map1.keys():
        inv_map[map1.get(key)] = key
    return inv_map

Upvotes: -2

thodnev
thodnev

Reputation: 1612

Not something completely different, just a bit rewritten recipe from Cookbook. It's futhermore optimized by retaining setdefault method, instead of each time getting it through the instance:

def inverse(mapping):
    '''
    A function to inverse mapping, collecting keys with simillar values
    in list. Careful to retain original type and to be fast.
    >> d = dict(a=1, b=2, c=1, d=3, e=2, f=1, g=5, h=2)
    >> inverse(d)
    {1: ['f', 'c', 'a'], 2: ['h', 'b', 'e'], 3: ['d'], 5: ['g']}
    '''
    res = {}
    setdef = res.setdefault
    for key, value in mapping.items():
        setdef(value, []).append(key)
    return res if mapping.__class__==dict else mapping.__class__(res)

Designed to be run under CPython 3.x, for 2.x replace mapping.items() with mapping.iteritems()

On my machine runs a bit faster, than other examples here

Upvotes: -2

Related Questions