简体   繁体   中英

pythonic way to reverse a dict where values are lists?

I have a dictionary that looks something like this:

letters_by_number = {
  1: ['a', 'b', 'c', 'd'],
  2: ['b', 'd'],
  3: ['a', 'c'],
  4: ['a', 'd'],
  5: ['b', 'c']
}

I want to reverse it to look something like this:

numbers_by_letter = {
  'a': [1, 3, 4],
  'b': [1, 2, 5],
  'c': [1, 3, 5],
  'd': [1, 2, 4]
}

I know that I could do this by looping through (key, value) through letters_by_number , looping through value (which is a list), and adding (val, key) to a list in the dictionary. This is cumbersome and I feel like there must be a more "pythonic" way to do this. Any suggestions?

This is well-suited for collections.defaultdict :

>>> from collections import defaultdict
>>> numbers_by_letter = defaultdict(list)
>>> for k, seq in letters_by_number.items():
...     for letter in seq:
...         numbers_by_letter[letter].append(k)
... 
>>> dict(numbers_by_letter)
{'a': [1, 3, 4], 'b': [1, 2, 5], 'c': [1, 3, 5], 'd': [1, 2, 4]}

Note that you don't really need the final dict() call (a defaultdict will already give you the behavior you probably want), but I included it here because the result from your question is type dict .

Usesetdefault :

letters_by_number = {
    1: ['a', 'b', 'c', 'd'],
    2: ['b', 'd'],
    3: ['a', 'c'],
    4: ['a', 'd'],
    5: ['b', 'c']
}

inv = {}
for k, vs in letters_by_number.items():
    for v in vs:
        inv.setdefault(v, []).append(k)

print(inv)

Output

{'a': [1, 3, 4], 'b': [1, 2, 5], 'c': [1, 3, 5], 'd': [1, 2, 4]}

A (trivial) subclass of dict would make this very easy:

class ListDict(dict):
    def __missing__(self, key):
        value = self[key] = []
        return value


letters_by_number = {
  1: ['a', 'b', 'c', 'd'],
  2: ['b', 'd'],
  3: ['a', 'c'],
  4: ['a', 'd'],
  5: ['b', 'c']
}


numbers_by_letter = ListDict()
for key, values in letters_by_number.items():
    for value in values:
        numbers_by_letter[value].append(key)

from pprint import pprint
pprint(numbers_by_letter, width=40)

Output:

{'a': [1, 3, 4],
 'b': [1, 2, 5],
 'c': [1, 3, 5],
 'd': [1, 2, 4]}

Here's a solution using a dict comprehension, without adding list elements in a loop. Build a set of keys by joining all the lists together, then build each list using a list comprehension. To be more efficient, I've first built another dictionary containing sets instead of lists, so that k in v is an O(1) operation.

from itertools import chain

def invert_dict_of_lists(d):
    d = { i: set(v) for i, v in d.items() }
    return {
        k: [ i for i, v in d.items() if k in v ]
        for k in set(chain.from_iterable(d.values()))
    }

Strictly, dictionaries in modern versions of Python 3 retain the order that keys are inserted in. This produces a result where the keys are in the order they appear in the lists; not alphabetical order like in your example. If you do want the keys in sorted order, change for k in set(...) to for k in sorted(set(...)) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM