Traversing Down in a tree like dictionary in Python - python

I have a dictionary something like this:
{'A': [12343,
2342349,
{'B': [3423,
342349283,
73,
{'C': [-23,
-2342342,
36],
'D': [-2,
-2520206,
63]}],
'E': [-1.5119711426000446,
-1405627.5262916991,
26.110728689275614,
{'F': [-1.7211282679440503,
-1601770.8149339128,
113.9541439658396],
'G': [0.21282003105839883,
196143.28864221353,
-13.954143965839597,
{'H': [0.43384581412426826,
399408,
203],
'I': [-0.22,
-203265,
-103]}]}]}]}
I want a function using which I can get values.
example, traverse(dictionary,'F') and it should give me the output. Couldn't found any solution. I am able to traverse 1 or two level, but not more. Either the code will break or it will not stop.
My Current solution which is not working is:
def traverse(dictionary,search):
print "Traversing"
if isinstance(dictionary,dict):
keys = dictionary.keys()
print keys
if search in keys:
print "Found",search,"in",keys
print "Printing found dict",dictionary
print
print "Check this out",dictionary.get(search)
print "Trying to return"
val=dictionary.get(search)
return val
else:
for key in keys:
print 'Key >>>>>>>>>',dictionary.get(key)
print
temp=dictionary.get(key)[-1]
print "Temp >>>>>>>",temp
traverse(temp,search)

Assuming there's going to be only one matching key in any of your given data structure, you can use a function that recursively traverses the dictionary looking for the key and returning its value if found, and if not found it will raise an exception, so that the calling frame can catch it and move on to the next candidate key:
def traverse(dictionary, search):
for k, v in dictionary.items():
if k == search:
return v
if isinstance(v[-1], dict):
try:
return traverse(v[-1], search)
except ValueError:
pass
raise ValueError("Key '%s' not found" % search)
so that traverse(d, 'F') returns (assuming your dict is stored as variable d):
[-1.7211282679440503, -1601770.8149339128, 113.9541439658396]
On the other hand, if there can be multiple matches in the given data, you can make the function yield the value of a matching key instead so that the function becomes a generator that generates sub-lists of 0 to many matching keys:
def traverse(dictionary, search):
for k, v in dictionary.items():
if k == search:
yield v
if isinstance(v[-1], dict):
yield from traverse(v[-1], search)
so that list(traverse(d, 'F')) returns:
[[-1.7211282679440503, -1601770.8149339128, 113.9541439658396]]

You need to handle both dictionaries and lists to traverse your structure entirely. You currently only handle dictionaries, but the dictionary with the 'F' key in it is an element of a list object, so you can't find it with your method.
While you can use recursion to make use of the function call stack to track the different levels of your structure, I'd do it iteratively and use a list or collections.deque() (faster for this job) to do track the objects still to process. That's more efficient and won't run into recursion depth errors for larger structures.
For example, walking all the elements with a generator function, then yielding each element visited, could be:
from collections import deque
def walk(d):
queue = deque([d])
while queue:
elem = queue.popleft()
if isinstance(elem, dict):
queue.extend(elem.values())
elif isinstance(elem, list):
queue.extend(elem)
yield elem
The above uses a queue to process elements breath first; to use it as a stack, just replace queue.popleft() with queue.pop().
You can then use the above walker to find your elements:
def search_key(obj, key):
for elem in walk(obj):
if isinstance(elem, dict) and key in elem:
return elem
For your dictionary, the above returns the first dictionary that contains the looked-for key:
>>> search_key(dictionary, 'F')
{'F': [-1.7211282679440503, -1601770.8149339128, 113.9541439658396], 'G': [0.21282003105839883, 196143.28864221353, -13.954143965839597, {'H': [0.43384581412426826, 399408, 203], 'I': [-0.22, -203265, -103]}]}
>>> _['F']
[-1.7211282679440503, -1601770.8149339128, 113.9541439658396]
If you are only ever interested in the value for the given key, just return that, of course:
def search_key(obj, key):
for elem in walk(obj):
if isinstance(elem, dict) and key in elem:
return elem[key]

Related

How to delete dictionary values simply at a specific key 'path'?

I want to implement a function that:
Given a dictionary and an iterable of keys,
deletes the value accessed by iterating over those keys.
Originally I had tried
def delete_dictionary_value(dict, keys):
inner_value = dict
for key in keys:
inner_value = inner_value[key]
del inner_value
return dict
Thinking that since inner_value is assigned to dict by reference, we can mutate dict implcitly by mutating inner_value. However, it seems that assigning inner_value itself creates a new reference (sys.getrefcount(dict[key]) is incremented by assigning inner_value inside the loop) - the result being that the local variable assignment is deled but dict is returned unchanged.
Using inner_value = None has the same effect - presumably because this merely reassigns inner_value.
Other people have posted looking for answers to questions like:
how do I ensure that my dictionary includes no values at the key x - which might be a question about recursion for nested dictionaries, or
how do I iterate over values at a given key (different flavours of this question)
how do I access the value of the key as opposed to the keyed value in a dictionary
This is none of the above - I want to remove a specific key,value pair in a dictionary that may be nested arbitrarily deeply - but I always know the path to the key,value pair I want to delete.
The solution I have hacked together so far is:
def delete_dictionary_value(dict, keys):
base_str = f"del dict"
property_access_str = ''.join([f"['{i}']" for i in keys])
return exec(base_str + property_access_str)
Which doesn't feel right.
This also seems like pretty basic functionality - but I've not found an obvious solution. Most likely I am missing something (most likely something blindingly obvious) - please help me see.
If error checking is not required at all, you just need to iterate to the penultimate key and then delete the value from there:
def del_by_path(d, keys):
for k in keys[:-1]:
d = d[k]
return d.pop(keys[-1])
d = {'a': {'b': {'c': {'d': 'Value'}}}}
del_by_path(d, 'abcd')
# 'Value'
print(d)
# {'a': {'b': {'c': {}}}}
Just for fun, here's a more "functional-style" way to do the same thing:
from functools import reduce
def del_by_path(d, keys):
*init, last = keys
return reduce(dict.get, init, d).pop(last)
Don't use a string-evaluation approach. Try to iteratively move to the last dictionary and delete the key-value pair from it. Here a possibility:
def delete_key(d, value_path):
# move to most internal dictionary
for kp in value_path[:-1]:
if kp in dd and isinstance(d[kp], dict):
d = d[kp]
else:
e_msg = f"Key-value delete-operation failed at key '{kp}'"
raise Exception(e_msg)
# last entry check
lst_kp = value_path[-1]
if lst_kp not in d:
e_msg = f"Key-value delete-operation failed at key '{lst_kp}'"
raise Exception(e_msg)
# delete key-value of most internal dictionary
print(f'Value "{d[lst_kp]}" at position "{value_path}" deleted')
del d[lst_kp]
d = {1: 2, 2:{3: "a"}, 4: {5: 6, 6:{8:9}}}
delete_key(d, [44, 6, 0])
#Value "9" at position "[4, 6, 8]" deleted
#{1: 2, 2: {3: 'a'}, 4: {5: 6, 6: {}}}

get the value of the deepest nested dict in python

I have a dictionary like
{a:{b:{c:{d:2}}}, e:2, f:2}
How am I supposed to get the value of d and change it in python? Previous questions only showed how to get the level of nesting but didn't show how to get the value. In this case, I do not know the level of nesting of the dict. Any help will be appreciated, thank you!!!
P.S. I am using python 3
How about some recursion?
def unest(data, key):
if key in data.keys():
return data.get(key)
else:
for dkey in data.keys():
if isinstance(data.get(dkey), dict):
return unest(data.get(dkey), key)
else:
continue
d = {'a':{'b':{'c':{'d':25}}}, 'e':2, 'f':2}
r = unest(d, 'd')
# 25
r = unest(d, 'c')
# {'d': 25}
Edit: As Paul Rooney points out we don't have to use data.keys, we can simply iterate over the keys by doing if key in data
Edit 2: Given the input you provided this would find the deepest level, however this does not cover certain cases, such as where for example, key e is also a nested dictionary that goes n + 1 levels where n is equal to the depth of key a.
def find_deepest(data):
if not any([isinstance(data.get(k), dict) for k in data]):
return data
else:
for dkey in data:
if isinstance(data.get(dkey), dict):
return find_deepest(data.get(dkey))
else:
continue
u = find_deepest(d)
# {'d': 2}
I was searching for a similar solution, but I wanted to find the deepest instance of any key within a (possibly) nested dictionary and return it's value. This assumes you know what key you want to search for ahead of time. It's unclear to me if that is a valid assumption for the original question, but I hope this will help others if they stumble on this thread.
def find_deepest_item(obj, key, deepest_item = None):
if key in obj:
deepest_item = obj[key]
for k, v in obj.items():
if isinstance(v,dict):
item = find_deepest_item(v, key, deepest_item)
if item is not None:
deepest_item = item
return deepest_item
In this example, the 'd' key exists at the top level as well:
d = {'a':{'b':{'c':{'d':25}}}, 'd':3, 'e':2, 'f':2}
v = find_deepest_item(d, 'd')
# 25
Search for 'c':
v = find_deepest_item(d, 'c')
# {'d': 25}

Get key by more than one value in dictionary?

Maybe the dict is not intended to be used in this way, but I need to add more than one value to the same key. My intension is to use a kind of transitory property. If my dict is A:B and B:C, than I want to have the dict A:[B,C].
Let's make an example in order to explain better what I'd like to do:
numDict={'60':['4869'], '4869':['629'], '13':['2']}
I want it to return:
{'60':['4869','629'], '13':['2']}
For just two elements, it is possible to use something like this:
result={}
for key in numDict.keys():
if [key] in numDict.values():
result[list(numDict.keys())[list(numDict.values()).index([key])]]=[key]+numDict[key]
But what about if I have more elements? For example:
numDict={'60':['4869'], '4869':['629'], '13':['2'], '629':['427'}
What can I do in order to get returned {'60':[4869,629,427'], '13':['2']}?
def unchain(d):
#assemble a collection of keys that are not also values. These will be the keys of the final dict.
top_level_keys = set(d.keys()) - set(d.values())
result = {}
for k in top_level_keys:
chain = []
#follow the reference chain as far as necessary.
value = d[k]
while True:
if value in chain: raise Exception("Referential loop detected: {} encountered twice".format(value))
chain.append(value)
if value not in d: break
value = d[value]
result[k] = chain
return result
numDict={'60':'4869', '4869':'629', '13':'2', '629':'427'}
print(unchain(numDict))
Result:
{'60': ['4869', '629', '427'], '13': ['2']}
You might notice that I changed the layout of numDict since it's easier to process if the values aren't one-element lists. But if you're dead set on keeping it that way, you can just add d = {k:v[0] for k,v in d.items()} to the top of unchain, to convert from one to the other.
You can build your own structure, consisting of a reverse mapping of (values, key), and a dictionary of (key, [values]). Adding a key, value pair consists of following a chain of existing entries via the reverse mapping, until it finds the correct location; in case it does not exist, it introduces a new key entry:
class Groupir:
def __init__(self):
self.mapping = {}
self.reverse_mapping = {}
def add_key_value(self, k, v):
self.reverse_mapping[v] = k
val = v
key = k
while True:
try:
self.reverse_mapping[val]
key = val
val = self.reverse_mapping[val]
except KeyError:
try:
self.mapping[val].append(v)
except KeyError:
self.mapping[val] = [v]
break
with this test client:
groupir = Groupir()
groupir.add_key_value(60, 4869)
print(groupir.mapping)
groupir.add_key_value(4869, 629)
print(groupir.mapping)
groupir.add_key_value(13, 2)
print(groupir.mapping)
groupir.add_key_value(629, 427)
print(groupir.mapping)
outputs:
{60: [4869]}
{60: [4869, 629]}
{60: [4869, 629], 13: [2]}
{60: [4869, 629, 427], 13: [2]}
Restrictions:
Cycles as mentioned in comments.
Non unique keys
Non unique values
Probably some corner cases to take care of.
I have written a code for it. See if it helps.
What I have done is to go on diving in till i can go (hope you understand this statement) and mark them as visited as they will no longer be required. At the end I filter out the root keys.
numDict={'60':['4869'], '4869':['629'], '13':['2'], '629':['427']}
l = list(numDict) # list of keys
l1 = {i:-1 for i in numDict} # to track visited keys (initialized to -1 initially)
for i in numDict:
# if key is root and diving in is possible
if l1[i] == -1 and numDict[i][0] in l:
t = numDict[i][0]
while(t in l): # dive deeper and deeper
numDict[i].extend(numDict[t]) # update the value of key
l1[t] = 1 # mark as visited
t = numDict[t][0]
# filter the root keys
answer = {i:numDict[i] for i in numDict if l1[i] == -1}
print(answer)
Output:
{'60': ['4869', '629', '427'], '13': ['2']}

Recursive dictionary modification in python

What would be the easiest way to go about turning this dictionary:
{'item':{'w':{'c':1, 'd':2}, 'x':120, 'y':240, 'z':{'a':100, 'b':200}}}
into this one:
{'item':{'y':240, 'z':{'b':200}}}
given only that you need the vars y and b while maintaining the structure of the dictionary? The size or number of items or the depth of the dictionary should not matter, as the one I'm working with can be anywhere from 2 to 5 levels deep.
EDIT: I apologize for the type earlier, and to clarify, I am given an array of strings (eg ['y', 'b']) which I need to find in the dictionary and then keep ONLY 'y' and 'b' as well as any other keys in order to maintain the structure of the original dictionary, in this case, it would be 'z'
A better example can be found here where I need Chipset Model, VRAM, and Resolution.
In regards to the comment, the input would be the above link as the starting dictionary along with an array of ['chipset model', 'vram', 'resolution'] as the keep list. It should return this:
{'Graphics/Displays':{'NVIDIA GeForce 7300 GT':{'Chipset Model':'NVIDIA GeForce 7300 GT', 'Displays':{'Resolution':'1440 x 900 # 75 Hz'}, 'VRAM (Total)':'256 Mb'}}
Assuming that the dictionary you want to assign to an element of a super-dictionary is foo, you could just do this:
my_dictionary['keys']['to']['subdict']=foo
Regarding your edit—where you need to eliminate all keys except those on a certain list—this function should do the trick:
def drop_keys(recursive_dict,keep_list):
key_list=recursive_dict.keys()
for key in key_list:
if(type(recursive_dict[key]) is dict):
drop_keys(recursive_dict[key], keep_list)
elif(key not in keep_list):
del recursive_dict[key]
Something like this?
d = {'item': {'w': {'c': 1, 'd': 2}, 'x': 120, 'y': 240, 'z': {'a': 100, 'b': 200}}}
l = ['y', 'z']
def do_dict(d, l):
return {k: v for k, v in d['item'].items() if k in l}
Here's what I arrived at for a recursive solution, which ended up being similar to what #Dan posted:
def recursive_del(d,keep):
for k in d.copy():
if type(d[k]) == dict:
recursive_del(d[k],keep)
if len(d[k]) == 0: #all keys were deleted, clean up empty dict
del d[k]
elif k not in keep:
del d[k]
demo:
>>> keepset = {'y','b'}
>>> a = {'item':{'w':{'c':1, 'd':2}, 'x':120, 'y':240, 'z':{'a':100, 'b':200}}}
>>> recursive_del(a,keepset)
>>> a
{'item': {'z': {'b': 200}, 'y': 240}}
The only thing I think he missed is that you will need to sometimes need to clean up dicts which had all their keys deleted; i.e. without that adjustment you would end up with a vestigial 'w':{} in your example output.
Using your second example I made something like this, it's not exactly pretty but it should be easy to extend. If your tree starts to get big, you can define some sets of rules to parse the dict.
Each rule here are actually pretty much "what should I do when i'm in which state".
def rule2(key, value):
if key == 'VRAM (Total)':
return (key, value)
elif key == 'Chipset Model':
return (key, value)
def rule1(key, value):
if key == "Graphics/Displays":
if isinstance(value, dict):
return (key, recursive_checker(value, rule1))
else:
return (key, value)
else:
return (key, recursive_checker(value, rule2))
def recursive_checker(dat, rule):
def inner(item):
key = item[0]
value = item[1]
return rule(key, value)
return dict(filter(lambda x: x!= None, map(inner, dat.items())))
# Important bits
print recursive_checker(data, rule1)
In your case, as there is not many states, it isn't worth doing it but in case you have multiple cards and you don't necessarly know which key should be traversed but only know that you want certain keys from the tree. This method could be used to search the tree easily. It can be applied to many things.

Find a key inside a deeply nested dictionary

I have a lot of nested dictionaries, I am trying to find a certain key nested inside somewhere.
e.g. this key is called "fruit". How do I find the value of this key?
#Håvard's recursive solution is probably going to be OK... unless the level of nesting is too high, and then you get a RuntimeError: maximum recursion depth exceeded. To remedy that, you can use the usual technique for recursion removal: keep your own stack of items to examine (as a list that's under your control). I.e.:
def find_key_nonrecursive(adict, key):
stack = [adict]
while stack:
d = stack.pop()
if key in d:
return d[key]
for k, v in d.iteritems():
if isinstance(v, dict):
stack.append(v)
The logic here is quite close to the recursive answer (except for checking for dict in the right way;-), with the obvious exception that the recursive calls are replaced with a while loop and .pop and .append operations on the explicit-stack list, stack.
(Making some wild guesses about your data structure...)
Do it recursively:
def findkey(d, key):
if key in d: return d[key]
for k,subdict in d.iteritems():
val = findkey(subdict, key)
if val: return val
Just traverse the dictionary and check for the keys (note the comment in the bottom about the "not found" value).
def find_key_recursive(d, key):
if key in d:
return d[key]
for k, v in d.iteritems():
if type(v) is dict: # Only recurse if we hit a dict value
value = find_key_recursive(v, key)
if value:
return value
# You may want to return something else than the implicit None here (and change the tests above) if None is an expected value
Almost 11 years later... based on Alex Martelli answer with slight modification, for Python 3 and lists:
def find_key_nonrecursive(adict, key):
stack = [adict]
while stack:
d = stack.pop()
if key in d:
return d[key]
for v in d.values():
if isinstance(v, dict):
stack.append(v)
if isinstance(v, list):
stack += v
I have written a handy library for this purpose.
I am iterating over ast of the dict and trying to check if a particular key is present or not.
Do check this out. https://github.com/Agent-Hellboy/trace-dkey
An example from README
>>> from trace_dkey import trace
>>> l={'a':{'b':{'c':{'d':{'e':{'f':1}}}}}}
>>> print(trace(l,'f'))
[['a', 'b', 'c', 'd', 'e', 'f']]
Now you can query it as l['a']['b']['c']['d']['e']['f']
>>> l['a']['b']['c']['d']['e']['f']
1

Categories