Reputation: 355
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
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
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
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
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
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
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