Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One-liner to create dictionary of lists

Tags:

python

how to do this list/dict comprehension turn this [("a", 1), ("b", 2), ("a", 3)]

into this

{
  "a": [1, 3],
  "b": [2]
}

I know how to do it in a for loop, can I use just one line to do the work?

like image 628
Liao Zhuodi Avatar asked Jun 16 '18 04:06

Liao Zhuodi


2 Answers

A simple approach is to use a simple collections.defaultdict() of lists:

from collections import defaultdict

lst = [("a", 1), ("b", 2), ("a", 3)]

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

print(items)

Which creates:

defaultdict(<class 'list'>, {'a': [1, 3], 'b': [2]})

Note: If you want the final result to be a normal dictionary, you can just wrap dict().

If you really want a one liner, you could use itertools.groupby() in a dict comprehension:

>>> from itertools import groupby
>>> from operator import itemgetter
>>> lst = [("a", 1), ("b", 2), ("a", 3)]
>>> {k: list(map(itemgetter(1), g)) for k, g in groupby(sorted(lst, key=itemgetter(0)), key=itemgetter(0))}
{'a': [1, 3], 'b': [2]}

Which can also be written more cleanly as:

{k: [x[1] for x in g] for k, g in groupby(sorted(lst, key=itemgetter(0)), key=itemgetter(0))}

The above solution has O(NlogN) complexity due to sorting, a requirement if you want to group similar items together. This is less efficient than The first defaultdict solution, which is O(N), since you only need to iterate the list once. The first solution would be more preferable, since its easier to read, efficient and maintainable.

like image 170
RoadRunner Avatar answered Sep 22 '22 19:09

RoadRunner


Personally, I think using a loop is a simple option here, but for the sake of demonstration, you can define a custom class here that inherits from collections.MutableMapping to accomplish your goal.

import collections

class AutoListDict(collections.MutableMapping):

  def __init__(self, values=()):            
      self.inner = collections.defaultdict(list)
      for k, v in values:
          self[k].append(v)

  def __setitem__(self, k, v): self.inner[k] = v
  def __getitem__(self, k):return self.inner[k]
  def __delitem__(self, k_): del self.inner[k]
  def __iter__(self): return iter(self.inner)
  def __len__(self): return len(self.inner)
  def __repr__(self): return str(dict(self.inner))

This initializes a defaultdict from your input values, so instead of overwriting a key it appends to it. In action, you can simply call the class constructor on your array of tuples:

>>> arr = [('a', 1), ('b', 2), ('a', 3)]
>>> AutoListDict(arr)
{'a': [1, 3], 'b': [2]}

Or if you want something more similar to a dictionary comprehension:

>>> dct = AutoListDict((k, v) for k, v in arr)
>>> dct
{'a': [1, 3], 'b': [2]}
like image 31
user3483203 Avatar answered Sep 23 '22 19:09

user3483203