5

For example I have

x = ['a','b','c']

I need to convert it to:

y['a']['b']['c'] = ''

Is that possible?

For the background, I have a config file which contains dotted notation that points to a place in some json data. I'd like to use the dotted notation string to access that specific data in the json file. For example, in the config:

path_to_data = "user.name.first_name"

I'd like my script to recognize that as:

json_data["user"]["name"]["first_name"]

so I can get the value of the first_name field. I converted the original string into a list, and now I don't know how to convert it to a nested dict.

EDIT: There is an existing data structure that I need to apply the dict with. Let's say:

m = {'a': {'b': {'c': 'lolcat'}}}

so that

m['a']['b']['c']

gives me 'lolcat'. If I get the right dictionary structure (as some of the replies did), I would still need to apply this to the existing dictionary 'm'.

So, again, I get this from a config file:

c = 'a.b.c'

That I converted to a list, thinking this will make things easier:

x = ['a','b','c']

Now I have a json-like data structure:

m = {'a': {'b': {'c': 'lolcat'}}}

So the nested dict generated from 'x' should be able to traverse 'm' so that

m['a']['b']['c']

gets me the cat.

Arbie Samong
  • 1,195
  • 1
  • 15
  • 17

6 Answers6

6
li = ['a','b','c']

d = reduce(lambda x, y: {y:x}, reversed(li+['']))

print(d)
print(d['a']['b']['c'])
Petar Ivanov
  • 91,536
  • 11
  • 82
  • 95
  • 3
    Without saying that your solution is bad, I would like to point to [this answer](http://stackoverflow.com/questions/1892324/why-program-functionally-in-python/1892614#1892614) of Alex Martelli for some criticism of using lambdas and reduce. – Björn Pollex Jul 14 '11 at 09:27
4

I guess you also want to include a value in the end. This works for that too:

def get_value(d, l):
    if len(l) > 1:
        return get_value(d[l[0]], l[1:])
    return d[l[0]]

def add_keys(d, l, c=None):
    if len(l) > 1:
        d[l[0]] = _d = {}
        d[l[0]] = d.get(l[0], {})
        add_keys(d[l[0]], l[1:], c)
    else:
        d[l[0]] = c

def main():
    d = {}
    l1 = ['a', 'b', 'c', 'd']
    c1 = 'letters'
    l2 = [42, "42", (42,)]
    c2 = 42
    add_keys(d, l1, c1)
    print d
    add_keys(d, l2, c2)
    print d

if __name__ == '__main__':
    main()

It prints:

{'a': {'b': {'c': {'d': 'letters'}}}}
{'a': {'b': {'c': {'d': 'letters'}}}, 42: {'42': {(42,): 42}}}
letters
42

So it surely works. Recursion for the win.

Adam Lear
  • 38,111
  • 12
  • 81
  • 101
Mihai Maruseac
  • 20,967
  • 7
  • 57
  • 109
1
>>> x = ['a','b','c']
>>> y={}
>>> y[x[-1]]=""
>>> x.pop(-1)
'c'
>>> for i in x[::-1]:
...     y={i:y}
...
>>> y
{'a': {'b': {'c': ''}}}
>>> y['a']['b']['c']
''
Rusty Rob
  • 16,489
  • 8
  • 100
  • 116
0

This will work.

#!/usr/bin/python2
from __future__ import print_function

x = ['a','b','c']

def ltod(l):
    rv = d = {}
    while l:
        i = l.pop(0)
        d[i] = {}
        d = d[i]
    return rv

d = ltod(x)
print(d)
print(d["a"]["b"]["c"])
d["a"]["b"]["c"] = "text"
print(d["a"]["b"]["c"])

Outputs:

{'a': {'b': {'c': {}}}}
{}
text
Keith
  • 42,110
  • 11
  • 57
  • 76
  • Keith, could you please explain why in your function you return rv instead of "d"? Why do we need to make a copy of "d"? I tried to return "d" and the dictionary that I got is empty! – bettas Jan 10 '17 at 13:55
  • The `rv = d = {}` line makes both `rv` and `d` refer to the same dictionary object initially. The `d` is mutated inside the loop (refers to sub-dictionary). the rv is still referencing the top-level dictionary which is the one we want to return. – Keith Jan 11 '17 at 04:59
0

Find below sample that is not very beautiful but quite simple:

path_to_data = "user.name.first_name"
keys = path_to_data.split('.')
t = []
for key in keys[::-1]: # just to iterate in reversed order
    if not t:
        t.append({k:{}})
    else:
        t[-1] = ({k: t[-1]})
#t[0] will contain your dictionary
Artsiom Rudzenka
  • 27,895
  • 4
  • 34
  • 52
0

A general solution would be to use collections.defaultdict to create a nested dictionary. Then override __setitem__ for whatever behavior you'd like. This example will do the string parsing as well.

from collections import defaultdict

class nesteddict(defaultdict):
    def __init__(self):
        defaultdict.__init__(self, nesteddict)
    def __setitem__(self, key, value):
        keys = key.split('.')
        for key in keys[:-1]:
            self = self[key]
        defaultdict.__setitem__(self, keys[-1], value)

nd = nesteddict()
nd['a.b.c'] = 'lolcat'
assert nd['a']['b']['c'] == 'lolcat'
A. Coady
  • 54,452
  • 8
  • 34
  • 40