Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In python, how do I invert a 2D dictionary?

I have a dictionary in the following form

dict = {
   "a" : {"a1" : 1},
   "b" : {"a2" : 1, "a3" : 2},
   "c" : {"a2" : 3, "a4" : 3}
}

and I need the reverse index dictionary, in this form:

inverseDict = {
    "a1" : {"a" : 1},
    "a2" : {"b" : 1, "c" : 3},
    "a3" : {"b" : 2},
    "a4" : {"c" : 3}
}

Basically

inverseDict = {dict.value.key : { dict.key : dict.value.value}}

So essentially, I need the keys of the values as keys, and the keys as keys of values, while at the same time joining results for duplicate new keys etc.

I've tried to do

ks = dict.keys()
vals = dict.values()

ks2 = vals.keys()
vals2 = vals.values()

if this makes any sense

But I'm getting an error

'dict_values' object has no attribute 'keys'

Which from what I understand is because dict.values() .keys() .items() return "views" instead of the actual element itself, but I don't know hot to go about fixing this problem.

Also is there a more efficient solution I should consider, because my actual dict is pretty large (~10k keys), and the resulting inverse dict will also be large ( >3k keys)

like image 297
George Avatar asked Nov 05 '18 20:11

George


3 Answers

using collections.defaultdict(dict) and a double loop it's rather easy:

d = {
    "a" : {"a1" : 1},
    "b" : {"a2" : 1, "a3" : 2},
    "c" : {"a2" : 3, "a4" : 3},
}

import collections

inverted = collections.defaultdict(dict)

for key,subd in d.items():
    for k,v in subd.items():  # no inspiration for key/value names...
        inverted[k][key] = v

inverted is

{'a1': {'a': 1},
 'a2': {'b': 1, 'c': 3},
 'a3': {'b': 2},
 'a4': {'c': 3}}

using defaultdict avoids to test if the entry already exists & creates a dictionary-value if it doesn't. So, just add the key/values brainlessly in the expected order.

Note that those problems where you need to deal items into several objects are hardly solved using comprehensions.

like image 89
Jean-François Fabre Avatar answered Oct 12 '22 04:10

Jean-François Fabre


You could use setdefault:

d = {
    'a': {'a1': 1},
    'b': {'a2': 1, 'a3': 2},
    'c': {'a2': 3, 'a4': 3}
}

result = {}
for ok, vs in d.items():
    for ik, v in vs.items():
        result.setdefault(ik, {})[ok] = v

print(result)

Output

{'a4': {'c': 3}, 'a1': {'a': 1}, 'a2': {'c': 3, 'b': 1}, 'a3': {'b': 2}}

The function setdefault has a similar effect to the usage of defaultdict.

like image 22
Dani Mesejo Avatar answered Oct 12 '22 05:10

Dani Mesejo


Another solution sans standard lib... But Jean-Francois Fabre's answer is more concise and probably easier to modularize. When in doubt, use the standard lib.

OriginalDict = ... (Original dict items)
InvertedDict = {}
for k, v in OriginalDict.items():
    for k_, v_ in v.items():
        if InvertedDict.get(k_) is None:
            InvertedDict[k_] = {}
        InvertedDict[k_][k] = v_
like image 1
Quentin Avatar answered Oct 12 '22 05:10

Quentin