Smelly Cat
Smelly Cat

Reputation: 33

Convert String to multi-level dictionary keys?

I am giving the user the ability to check a specific key in a multi-level dictionary. My idea is that they will pass the path to the key like this:

root.subelement1.subelement2.key

This can be of arbitrary length and depth.

Once I have the string (above) from the user, I'll split it and get a list of each individual component:

elements = ['root', 'subelement1', 'subelement2', 'key']

All of this I can do. The next part is where I am stuck. How can I query the dictionary key, specified by the above when it's arbitrary length?

My initial thought was to do something like my_dict[elements[0]][elements[1]]...but that doesn't scale or work when my user doesn't pass exactly the length I expect.

How can I get the data at an arbitrary key depth, in this case?


A couple examples:

Upvotes: 3

Views: 1415

Answers (5)

Jean-François Fabre
Jean-François Fabre

Reputation: 140168

you could do that using reduce which will query the keys in the nested dictionaries:

q = "district.District15.HenryBristow.principal"
my_dict  = {"district" : {"District15" : {"HenryBristow" : {"principal" : 12}}}}

from functools import reduce  # python 3 only

print(reduce(lambda x,y : x[y],q.split("."),my_dict))

result:

12

If you want to avoid to catch KeyError in case the data doesn't exist with this path, you could use get with a default value as empty dictionary:

reduce(lambda x,y : x.get(y,{}),q.split("."),my_dict)

Trying to get an unknown value returns an empty dictionary. The only drawback is that you don't know from where exactly the path got lost, so maybe leaving the KeyError be raised wouldn't be so bad:

try:
    v = reduce(lambda x,y : x[y],q.split("."),my_dict)
except KeyError as e:
    print("Missing key: {} in path {}".format(e,q))
    v = None

Upvotes: 6

Nozar Safari
Nozar Safari

Reputation: 505

u can do this like below

my_dict = someDict

tmpDict = dict(someDict)   # a coppy of dict
input = "x.u.z"
array = input.split(".")
for key in array:
    tmpDict = tmpDict[key]
print(tmpDict)

but your question is very challenging: u say if user send country.us then go to my-dict.country.us

but what happen if one of this path in my_dict be a list code will results error u can handle this by check type

if isinstance(tmpDict , dict ):
    tmpDict = tmpDict[key]
else:
    # u should say what u want else (a Recursive method u will need)

edit if user address maybe wrong you should check my_dict have this field or not sample code is below but will be many if i don't like that! if key not in tmpDict: print("Bad Path") return

Upvotes: 0

Eric Chiesse
Eric Chiesse

Reputation: 474

Use recursion. Ex:

root = {
    'subelement1': {
        'subelement2': {
            'key': 'value'
        }
    }
}

elements = ['subelement1', 'subelement2', 'key']


def getElem(d, keys):
    if keys == []:
        return None
    else:
        key = keys[0]
        remainingKeys = keys[1:]
        if remainingKeys == []:
            return d[key]
        else:
            if type(d[key]) == dict:
                return getElem(d[key], remainingKeys)
            else:
                return None


print(getElem(root, elements))

Upvotes: 2

Ajax1234
Ajax1234

Reputation: 71451

You can transverse the dictionary using a for loop:

s = 'root.subelement1.subelement2.key'
d1 = {'root':{'subelement1':{'subelement2':{'key':15, 'key1':18}}}}
new_d = d1
for key in s.split('.'):
    new_d = new_d[key]

print(new_d)

Output:

15

Upvotes: 0

packrat
packrat

Reputation: 1

from a python 2.x perspective, you can do this with reduce.

query_list = keys.split(":")
print reduce(lambda x,y: x[y], [my_dict] + query_list)

But in general, you'll want to do this with a recursive or iterative function if you want to do error handling beyond throwing a KeyError.

Upvotes: 0

Related Questions