jimscafe
jimscafe

Reputation: 1091

Python parse a string into n-depth dictionary

I have a string of keys separated by a delimiter | followed by a value.

The string might be

key1|key2|key3=value

This should parse as

dct[key1][key2][key3] = value

The individual dictionaries might already exist.

To get the prototype started I have hard coded the creation of the dictionaries like this (fields is the main dictionary - already existing, s is the sequence of keys separated by | (pipe) :

   keys=s.split('|')     
   if len(keys) == 1:
        fields[keys[0]] = field_data
   elif len(keys) == 2:
        if keys[0] not in fields:
            fields[keys[0]] = {}
        fields[keys[0]][keys[1]] = field_data

I extended this for three-depth dictionary, but it is very inelegant I know.

Upvotes: 0

Views: 210

Answers (3)

user4237459
user4237459

Reputation:

This will assume that your keys and values are strings. I.E:

key1|key2|key3=value
dct['key1']['key2']['key3'] = 'value'
# The literal strings

Then you can create a function that takes a string and splits it.

def str_as_key(string, dictionary=None):
    dictionary = dictionary or {}
    keys = string.split("|")
    keys[-1:] = keys[-1].split("=")
    value = keys.pop()
    dicti = [dictionary, []]
    for key in keys:
        dicti = [dicti[0].get(key), dicti[1] + [key]]
        if not isinstance(dicti[0], dict):
            exec("dictionary['{0}'] = {1}; dicti[0] = {1}".format("']['".join(dicti[1]), '{}'))
    exec("dictionary['{}'] = '{}'".format("']['".join(keys), value))
    return dictionary

The thing about this method is that it doesn't have to create a new dictionary. It can update one and leave other keys untouched. e.g:

>>> s = "key1|key2|key3=value"
>>> str_as_key(s, {})
{'key1': {'key2': {'key3': 'value'}}}
>>> str_as_key(s, {"key1": {"key2": 12, "foo": "bar"}, "pi": 3.14})
{'pi': 3.14, 'key1': {'foo': 'bar', 'key2': {'key3': 'value'}}}

Upvotes: 1

georg
georg

Reputation: 214969

For example,

s = "key1|key2|key3=value"

keys, value = s.split('=')
keys = keys.split('|')

base = {}

reduce(
    lambda d, k: d.setdefault(k, {}),
    keys[:-1],
    base
)[keys[-1]] = value

print base # {'key1': {'key2': {'key3': 'value'}}}

If you need this often, it might be worth making a class:

class NestedDict(dict):
    def __setitem__(self, key, value):
        if not isinstance(key, collections.Iterable) or isinstance(key, basestring):
            super(NestedDict, self).__setitem__(key, value)
            return
        keys = list(key)
        reduce(
            lambda d, k: d.setdefault(k, {}),
            keys[:-1],
            self
        )[keys[-1]] = value

and then:

s = "key1|key2|key3=value"
keys, value = s.split('=')

base = NestedDict()

base[keys.split('|')] = value

print base # {'key1': {'key2': {'key3': 'value'}}}

Upvotes: 2

user1907906
user1907906

Reputation:

Why do you need a dictionary with three dimensions? Use a simple dictionary with tuples as keys:

s = "key1|key2|key3=value"
ks, v = s.split("=")
dct = {}
dct[ks] = v

Now you can access dct like this:

print(dct[("key1", "key2", "key3")])

Output:

value

Upvotes: 2

Related Questions