Bamaboy
Bamaboy

Reputation: 239

Is there any simple way to swap python dictionary key values where values are list

Is there any simple way to swap python dictionary key values where values are list My dictionary is like following

d={1:[1,2,3,4],2:[2,3,4],5:[1,3,6,7]}

I want to generate a dictionary from it like following

a={1:[1,5],2:[1,2],3:[1,2,5],4:[1,2],6:[5],7:[5]}

I tested with reversed

dict(map(reversed, d.items())

It won't iterate and create keys with items in the list returns TypeError: unhashable type: 'list'

I am looking for any inline methods available for achieving this

Upvotes: 3

Views: 765

Answers (3)

chepner
chepner

Reputation: 531325

This isn't really suitable for a one-liner; you are oversimplifying what you need to do by calling it a simple reversal.

A real reversal would simply map values to keys instead of keys to values, which you can do (with the slight but necessary change of lists to tuples):

>>> print dict((tuple(d[k]), k) for k in d)
{(1, 3, 6, 7): 5, (2, 3, 4): 2, (1, 2, 3, 4): 1}

What you want is far more complicated, and is commonly called a transposition of the dict.

from operator import itemgetter as ig
from itertools import groupby

transposed_dict = dict((k, map(ig(1), v)) 
                       for k, v in groupby(
                                      sorted((nk, k) for k in d for nk in d[k]),
                                             key=ig(0)))

There's nothing particularly simple about this, although conceptually it isn't too bad:

  1. (nk, k) for k in d for nk in d[k] creates an expanded association list from the dictionary, with the keys and values inverted.

  2. groupby collects all the tuples with a common first element

  3. (k, map(ig(1), v)) collects the tuples with a common first element into a single tuple: (1,1), (1,2) => (1, [1,2]).

  4. The tuples from step 3 are used to build the new dictionary.

You are far better off, however, using a simple 3-line for loop as shown by Willem Van Onsem; the essence of that loop is in the generator expression used by sorted; everything else is just dealing with the attempt to avoid mutable variables. Not everything should (or can) be reduced to a simple one-liner.

Upvotes: 0

Tom Karzes
Tom Karzes

Reputation: 24052

This will work:

def revdict(d):
    r = {}
    for k in d:
        for v in d[k]:
            if v not in r:
                r[v] = [k]
            else:
                r[v].append(k)
    return r

Then you can do:

d={1:[1,2,3,4],2:[2,3,4],5:[1,3,6,7]}
a = revdict(d)
print(a)

If you want to avoid having to check for new keys, you can use a defaultdict, then always append.

Upvotes: 3

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476709

Your approach does not work, since here you aim to construct a dictionary:

{[1,2,3,4]: 1, [2,3,4]: 2, [1,3,6,7]: 5}

but since lists are unhashable, these can not be used as keys (and furthermore it is not what you intend to construct anyway).

You probably better use a defaultdict for this:

from collections import defaultdict

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

after this opertion, result is a defaultdict (a subclass of the vanilla dict) which maps items in the list of values to keys (that contained that value). Like:

>>> result
defaultdict(<class 'list'>, {1: [1, 5], 2: [1, 2], 3: [1, 2, 5], 4: [1, 2], 6: [5], 7: [5]})

You can optionally use:

result = dict(result)

to create a new dictionary with these values (and thus drop the defaultdict).

Mind that:

  • since most Python interpreters do not order the dictionary (it is definitely not a hard assumption you can make), the order of the elements in the list might be different; and
  • the items in the lists in your dictionary d should be hashable.

Upvotes: 2

Related Questions