简体   繁体   中英

How can I construct a nested dictionary?

I have a list of strings, from which I have to construct a dict. So, for example, I have:

foo.bar:10
foo.hello.world:30
xyz.abc:40
pqr:100

This is represented as a dict:

{
  "foo": {
    "bar": 10,
    "hello": {
      "world": 30
    }
  },
  "xyz": {
    "abc": 40
  },
  "pqr": 100
}

This question is based on the same premise, but the answers discuss hardcoded depths such as:

mydict = ...
mydict['foo']['bar'] = 30

Since the dot seperated strings on the left may be of any depth, I can't figure out a way to build the dict. How should I parse the dot separated string and build the dict?

Building upon the solution in the links, you could

  1. iterate over each line
  2. for each line, extract a list of keys, and its value
  3. recurse into a dictionary with each key using setdefault
  4. assign the value at the bottom

lines = \
'''
foo.bar:10
foo.hello.world:30
xyz.abc:40
pqr:100
'''.splitlines()

d = {}

for l in lines:
    k, v = l.split(':')
    *f, l = k.split('.')

    t = d
    for k in f:
        t = t.setdefault(k, {})
    t[l] = int(v)   # don't perform a conversion if your values aren't numeric
print(d)
{
    "pqr": 100,
    "foo": {
        "bar": 10,
        "hello": {
            "world": 30
        }
    },
    "xyz": {
        "abc": 40
    }
}

Recursive setdefault traversal learned from here .


Breaking down each step -

  1. Split on : , extract the key-list string and the value

     k, v = l.split(':') 
  2. Split the key-string on . to get a list of keys. I take the opportunity to partition the keys as well, so I have a separate reference to the last key that will be the key to v .

     *f, l = k.split('.') 

    *f is the catch-all assignment, and f is a list of any number of values (possibly 0 values, if there's only one key in the key-string!)

  3. For each key k in the key list f , recurse down into the "tree" using setdefault . This is similar to recursively traversing a linked list.

     for k in f: t = t.setdefault(k, {}) 
  4. At the end, the last key value pair comes from l and v .

     t[l] = v 

What's wrong with incrementally building it?

mydict = {}
mydict["foo"] = {}
mydict["foo"]["bar"] = 30
mydict["foo"]["hello"] = {}
mydict["foo"]["hello"]["world"] = 30
mydict["foo"]["xyz"] = {}
mydict["foo"]["xyz"]["abc"] = 40
mydict["foo"]["pqr"] = 100
# ...
pprint.pprint(mydict) # {'foo': {'bar': 30, 'hello': {'world': 30}, 'pqr': 100, 'xyz': {'abc': 40}}}

Including the parsing, you could use something like this:

import pprint

inp = """foo.bar:10
foo.hello.world:30
xyz.abc:40
pqr:100
"""

mydict = {}

for line in inp.splitlines():
    s, v = line.split(':')
    parts = s.split(".")
    d = mydict
    for i in parts[:-1]:
        if i not in d:
            d[i] = {}
        d = d[i]
    d[parts[-1]] = v

pprint.pprint(mydict) # {'foo': {'bar': '10', 'hello': {'world': 30'}}, 'pqr': '100', 'xyz': {'abc': '40'}}

One key point to consider in your case is that you either want to create a dictionary in a parent's dictionarys value part or an integer

x = """
foo.bar:10
foo.hello.world:30
xyz.abc:40
pqr.a:100
"""

tree = {}

for item in x.split():
    level, value = item.split(":")[0], item.split(":")[1]

    t = tree
    for part in item.split('.'):
        keyval = part.split(":")
        if len(keyval) > 1:
            #integer
            t = t.setdefault(keyval[0], keyval[1])
        else:
            t = t.setdefault(part, {})


import pprint
pprint.pprint(tree)

Result:

{'foo': {'bar': '10', 'hello': {'world': '30'}},
 'pqr': {'a': '100'},
 'xyz': {'abc': '40'}}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM