Engr Tanveer sultan
Engr Tanveer sultan

Reputation: 173

Inverting one-to-many mapping using dictionary comprehension

lst = [{'272': '4', '273': '4', '274': '4', '275': '5'}]
dct = {}

for k, v in lst[0].items():
    if dct.get(v) is None:
        dct.update({v: [k]})
    else:
        dct[v].append(k)

Output:

{'4': ['272', '273', '274'], '5': ['275']}

I can also write a nested comprehension:

dct = {v: [k for (k, v1) in lst[0].items() if v1 == v]
          for (k, v) in lst[0].items()}

Output is same:

{'4': ['272', '273', '274'], '5': ['275']}** 

But can we try to acheive the same result by using a single for loop in the dict comprehension?

Upvotes: 2

Views: 456

Answers (3)

Grigory Trofimov
Grigory Trofimov

Reputation: 1

This is one way to reverse one-to-many mapping. It does not require sorting. And in general map_reduce can help with many problems.

import more_itertools as mit

d = {'272': '4', '273': '4', '274': '4', '275': '5'}
dict(mit.map_reduce(d.items(), 
                    keyfunc=lambda el: el[1], 
                    valuefunc=itemgetter(0),
                   ))
# returns {'4': ['272', '273', '274'], '5': ['275']}

Upvotes: 0

Mad Physicist
Mad Physicist

Reputation: 114330

You can't quite do the same thing in a dict comprehension in one step. You're effectively inverting the dictionary, but since you don't have a 1-to-1 mapping in the original, you need to aggregate repeated keys for each value.

The list in the question is a red herring. I am going to use

d = {'272': '4', '273': '4', '274': '4', '275': '5'}

There are a couple of approaches you can take. The sane one is to keep a loop, but simplify it slightly. For example, you could use collections.defaultdict, which is like a regular dict, except it lets you automatically set missing keys with empty values:

from collections import defaultdict

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

You can write a comprehension for this if you use a couple of standard library functions though. One way is to use itertools.groupby, but that requires you to apply sorted first:

from itertools import groupby
from operator import itemgetter

result = {k: list(map(itemgetter(0), vs))
              for k, vs in groupby(sorted(d.items(),
                                          key=itemgetter(1, 0)),
                                   itemgetter(1))}

operator.itemgetter works like lambda x: x[0] or so here, but faster and more efficiently.

With the second solution, note that the time complexity went from O(n) to O(n log n) because of the sort, and you sacrifice a lot of legibility just to have a "one-liner".

Upvotes: 1

no comment
no comment

Reputation: 10230

A hack with a temporary helper dictionary:

dct = {v: h.setdefault(v, [k])
       for h in [{}]
       for k, v in lst[0].items()
       if v not in h or h[v].append(k)}

Upvotes: 1

Related Questions