Reputation: 13145
Is it possible to convert a tuple of tuples like this:
l = (("a","aa",1),("a","bb",2),("a","cc",1),("b","ee",9),("b","gg",2))
to a dict of dicts like this:
{"a":{"aa":1,"bb":2,"cc":1} "b": {"ee":9,"gg":2}}
using a dict comprehension like this:
r = {? for a,b,c in l}
Upvotes: 0
Views: 68
Reputation: 561
here is what I would probably use, because I feel it is fairly readable...
l = (("a","aa",1),("a","bb",2),("a","cc",1),("b","ee",9),("b","gg",2))
from collections import defaultdict
d = defaultdict(dict)
for a,b,c in l:
d[a][b]=c
print(d)
defaultdict(dict, {'a': {'aa': 1, 'bb': 2, 'cc': 1}, 'b': {'ee': 9, 'gg': 2}})
I did a crude benchmark against the other solution I like (groupby):
l = (("a","aa",1),("a","bb",2),("a","cc",1),("b","ee",9),("b","gg",2))
from collections import defaultdict
def dd():
d = defaultdict(dict)
for a,b,c in l:
d[a][b]=c
def gb():
{x: {z[1]: z[2] for z in y} for x, y in groupby(sorted(l, key=lambda x: x[0]), lambda x: x[0])}
def gb2():
first = itemgetter(0)
result = {key: {inner: value for _, inner, value in groups} for key, groups in groupby(l, key=first)}
%timeit dd()
%timeit gb()
%timeit gb2()
683 ns ± 1.33 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
2.11 µs ± 129 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
1.38 µs ± 29.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
(you see the added value of itemgetter vs having to do another import, also groupby expects a sorted input so for larger lists the performance penalty should be even worse...)
Upvotes: 0
Reputation: 61920
You could use groupby with a dictionary comprehension:
from itertools import groupby
from operator import itemgetter
l = (("a", "aa", 1), ("a", "bb", 2), ("a", "cc", 1), ("b", "ee", 9), ("b", "gg", 2))
first = itemgetter(0)
result = {key: {inner: value for _, inner, value in groups} for key, groups in groupby(l, key=first)}
print(result)
Output
{'b': {'gg': 2, 'ee': 9}, 'a': {'cc': 1, 'bb': 2, 'aa': 1}}
As mentioned by @juanpa.arrivillaga if the input is not sorted by the first element of each tuple, you need to sort it, for that you can do: l = sorted(l, key=first)
before using the dictionary comprehension.
Upvotes: 5
Reputation: 1936
In my opinion, Daniel Mesejo's answer nailed it. That answer is based on groupby
, but functionally speaking, groupby
is a form of reduce
. So, for variety, I'll offer here a solution with functools.reduce
(which also uses defaultdict
):
>>> from functools import reduce
>>> from collections import defaultdict
>>> l = (("a","aa",1),("a","bb",2),("a","cc",1),("b","ee",9),("b","gg",2))
>>> def update_and_return(acc, up):
... acc[up[0]][up[1]] = up[2]
... return acc
...
>>> reduce(update_and_return, l, defaultdict(dict))
defaultdict(<type 'dict'>, {'a': {'aa': 1, 'cc': 1, 'bb': 2}, 'b': {'ee': 9, 'gg': 2}})
Even though this isn't a dictionary comprehension solution, I hope it will provide more context for someone wanting to understand the computation required to answer the question.
Upvotes: 0
Reputation: 26037
Use itertools.groupby
:
from itertools import groupby
l = (("a","aa",1),("a","bb",2),("a","cc",1),("b","ee",9),("b","gg",2))
print({x: {z[1]: z[2] for z in y} for x, y in groupby(sorted(l, key=lambda x: x[0]), lambda x: x[0])})
# {'a': {'aa': 1, 'bb': 2, 'cc': 1}, 'b': {'ee': 9, 'gg': 2}}
Upvotes: 3
Reputation: 14226
Do you enjoy abusing built-ins
for not their intended purpose? I would honestly use the other answers here but this is also another way to do this.
from collections import defaultdict
d = defaultdict(dict)
any(d[x[0]].update({x[1]: x[-1]}) for x in l)
print(d)
defaultdict(dict, {'a': {'aa': 1, 'bb': 2, 'cc': 1}, 'b': {'ee': 9, 'gg': 2}})
Using any
will return a boolean
which clearly is not the intended purpose here hence why I recommend other answers listed.
Upvotes: 0