Python list of tuples to nested dict

Question:

I have a data structure as follows:

[(a,x1,1),(a,x2,5),(b,x1,1) ...]

and i want to turn it into a nested dictionary of the form

{a:{x1:1, x2:5}, b:{x1:1}...}

I tried

dictdata = {}
for row in rows:
    ean = row[1].encode('ascii','ignore')
    period = str(row[0])
    value = row[2]
    dictdata[ean]={} # init sub dictionary
    dictdata[ean][period] = value

but every time I do dictdata[ean]={}, the contents get erased, so this will not work. If I do not initialise the sub-dictionary, I also cannot get it to work.

Any help appreciated

Asked By: ohforf1sake

||

Answers:

You can do it in one statement

rows = [('a','x1',1),('a','x2',5),('b','x1',1)]
result = dict()
for key1, key2, value in rows:
  result.setdefault(key1, {}).update({key2: value})
Answered By: Muhammad Tahir

Same thing but using defaultdict

from collections import defaultdict
rows = [("a", "x1", 1), ("a", "x2", 5), ("b", "x1", 1)]

d = defaultdict(dict)
for k, v, v2 in rows:
    d[k][v] = v2
Answered By: Ali SAID OMAR

This is the kind of problem collections.defaultdict was built to solve:

https://docs.python.org/2/library/collections.html#collections.defaultdict

from collections import defaultdict
dictdata = defaultdict(dict)
rows = [('a','x1',1),('a','x2',5),('b','x1',1) ]
for row in rows:
    ean = row[1].encode('ascii','ignore')
    period = str(row[0])
    value = row[2]
    dictdata[period][ean] = value
dictdata

returns

{'a': {'x2': 5, 'x1': 1}, 'b': {'x1': 1}}
Answered By: greg_data

As a fix to your code, the proper way is to always if you have already the dictionary created, if not then instantiate one, like so:

>>> l
[('a', 'x1', 1), ('a', 'x2', 5), ('b', 'x1', 1)]
>>> d = {}
>>> for row in l:
        ean = row[1].encode('ascii','ignore')
        period = str(row[0])
        value = row[2]
        if period not in d:
            d[period] = {}
        if ean not in d[period]:
            d[period][ean] = {}
        d[period][ean] = value


>>> d
{'a': {b'x1': 1, b'x2': 5}, 'b': {b'x1': 1}}

You can also do it with defaultdict from collections, very straight forward:

>>> l = [('a','x1',1),('a','x2',5),('b','x1',1)]
>>>
>>> from collections import defaultdict
>>> 
>>> 
>>> d = defaultdict(dict)
>>> 
>>> for k, k_sub, v in l:
        d[k][k_sub] = v

>>> d
defaultdict(<class 'dict'>, {'a': {'x1': 1, 'x2': 5}, 'b': {'x1': 1}})
Answered By: Iron Fist

Solution for an arbitrary length of tuples:

l = [('a', 'x1', 1), ('a', 'x2', 5), ('b', 'x1', 1), ('a', 'x3', 'y1', 'z1', 7), ('a', 'x3', 'y1', 'z2', 666)]

def f(data):
    def _f(store, keys, value):
        if len(keys) == 1:
            return {keys[0]: value}
        store[keys[0]].update(_f(defaultdict(dict), keys[1:], value))
        return store
    result = defaultdict(dict)
    for a in data:
        _f(result, a[:-1], a[-1])
    return dict(result)

print(f(l))

{‘a’: {‘x1’: 1, ‘x2’: 5, ‘x3’: {‘y1’: {‘z2’: 666}}}, ‘b’: {‘x1’: 1}}

Answered By: Alexey Kubrinsky
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.