Reputation: 83828
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
Reputation: 226624
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'}
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)
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
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
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
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
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
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
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
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
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
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)
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
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
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
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
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
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
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:
for
loop. It also avoids using a list comprehension
for those who are bad at math ;-)Upvotes: 0
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
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
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
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
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
Reputation: 7
I would do it that way in python 2.
inv_map = {my_map[x] : x for x in my_map}
Upvotes: -1
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
Reputation: 4551
Another, more functional, way:
my_map = { 'a': 1, 'b':2 }
dict(map(reversed, my_map.items()))
Upvotes: 45
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
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
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
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
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