5

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)

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
George
  • 143
  • 7
  • 1
    Related: https://stackoverflow.com/questions/44570561/how-can-i-correct-the-error-attributeerror-dict-keys-object-has-no-attribut – pault Nov 05 '18 at 20:17
  • 1
    Just a quick comment, you'll want to avoid using the builtin keyword `dict` for assignment. You'll end up shadowing the `dict` builtin object. – r.ook Nov 05 '18 at 20:47
  • @Idlehands well it's not the actual name of my `dict`, just a "dummy" name. but yeah I see your point – George Nov 05 '18 at 21:06

3 Answers3

8

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.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • Well it most definitely works but I don't understand why `inverted=collections.defaultdict(dict)` is better that just using a `inverted = {}`. – George Nov 05 '18 at 20:49
  • 1
    because with a bare dict you have to test if dict already exists and create it if not: more lines, and slower. You can convert it back to `dict` if you want: `inverted = dict(inverted)` – Jean-François Fabre Nov 05 '18 at 20:51
  • but in both cases you create an empty `inverted` and go on to fill it up with values? am I not getting something? – George Nov 05 '18 at 21:08
  • with a default dict, just doing `inverted["a_key"]` _creates_ a dictionary under `a_key` key if doesn't exist. You don't have to worry about initialization of the dict. – Jean-François Fabre Nov 05 '18 at 21:10
1

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_
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
Quentin
  • 700
  • 4
  • 10
1

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.

Dani Mesejo
  • 61,499
  • 6
  • 49
  • 76
  • good solution. The only drawback is that it creates an empty dict object even if the key is found. `setdefault` is better when used with immutable objects that are more likely to be interned, or created once and stored in a variable that `setdefault` can reference – Jean-François Fabre Nov 05 '18 at 20:36