Chris Stenkamp
Chris Stenkamp

Reputation: 355

Python: One-Liner to create a dictionary of lists from a list of non-unique keys and values

Often in my programming experience, I want to create a dictionary of lists from a list, like this:

Input: A list of key-value pairs, with non-unique keys, like [("a", 1), ("a", 2), ("b", 3)]
Output: A dictionary where each of the non-unique keys has a list of its values, like {"a": [1,2], "b": [3]}.

Now I know I can achieve my desired result like this:

list_of_elems = [("a", 1), ("a", 2), ("b", 3)]
dict_of_elems = {}
for key, val in list_of_elems:
    if key in dict_of_elems:
        dict_of_elems[key].append(val)
    else:
        dict_of_elems[key] = [val]

(yes, I look before I leap here, but EAFP looks basically the same).

This works just fine, but it's 6 lines! I am sure that there must be a way in python to make a smart dict-comprehension that makes a one-liner out of this, but I cannot think of one! Does anybody have a good Idea?

Upvotes: 0

Views: 583

Answers (6)

dawg
dawg

Reputation: 103864

The only one liner I can think of (using ordinary tools and not using a side-effect comprehensions) is to use groupby:

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

di={k:[t[1] for t in v] for k,v in groupby(sorted(list_of_elems),key=lambda t:t[0])}

>>> di
{'a': [1, 2], 'b': [3]}

That is two lines if you count from itertools import groupby. Because of the sorted it is slower than the idiomatic:

di={}
for k,v in list_of_elems:
    di.setdefault(k, []).append(v)

Or with defaultdict:

from collections import defaultdict

di=defaultdict(list)
for k,v in list_of_elems:
    di[k].append(v)

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

Or subclass dict so that the default behavior is to return a list for a missing key (which is essentially what the .setdefault and defaultdict methods are doing):

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

di=Mydict()
for k,v in list_of_elems:
    di[k].append(v)

Upvotes: 1

Jamie Deith
Jamie Deith

Reputation: 714

This 2-liner works with 2 passes:

in_d = [("a", 1), ("a", 2), ("b", 3)]
output_dict = dict((e[0], []) for e in in_d)
_ = [output_dict[k].append(v) for (k, v) in in_d]

output_dict
# {'a': [1, 2], 'b': [3]}

Edit As correctly pointed out, the above modifies the dictionary as a side effect and better form is to implement the explicit loop:

in_d = [("a", 1), ("a", 2), ("b", 3)]
output_dict = dict((e[0], []) for e in in_d)
for (k, v) in in_d:
    output_dict[k].append(v)

output_dict
# {'a': [1, 2], 'b': [3]}

Using a defaultdict can take it to a single pass:

from collections import defaultdict
ddict = defaultdict(list)
for (k, v) in in_d:
    ddict[k].append(v)

ddict
# {'a': [1, 2], 'b': [3]}

Upvotes: 0

pygame
pygame

Reputation: 131

One liner can be: dict((k, [v for (kk, v) in list_of_elems if kk==k]) for (k,_ ) in list_of_elems)

Yet as robinsax said, complexity is worst and Secret Agent solution is preferable.

Upvotes: 1

wLui155
wLui155

Reputation: 742

A 2 liner but the downside is that map produces an extraneous list equivalent to size to the input.

dict_of_elms = {}                                                    # output dict
list(                                                                # force generator to be run
    map(                                                             # apply lambda to every element
        lambda tupElm : d[tupElm[0]].append(tupElm[1])               # append to corresponding dict key or create new list
            if dict_of_elms.get(tupElm[0], None) 
            else dict_of_elms.setdefault(tupElm[0], [tupElm[1]]),
        list_of_items                                                # list of size-two tuples to iterate on
    )
)

Also worth noting that despite being condensable within two lines it's far easier to space it out anyways...

Upvotes: 0

Secret Agent
Secret Agent

Reputation: 429

I don't have a one-liner, but here is a slightly more concise version:

list_of_elems = [("a", 1), ("a", 2), ("b", 3)]
dict_of_elems = {}
for key, val in list_of_elems:
    dict_of_elems.setdefault(key, []).append(val)

Upvotes: 3

robinsax
robinsax

Reputation: 1220

Any one-liner to achieve this will be difficult to read and likely slow (O(n^2)). I'd suggest just writing and reusing a function using the logic you have.

Upvotes: 0

Related Questions