Reputation: 13
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)
Upvotes: 0
Views: 1178
Reputation: 1121834
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]
Upvotes: 1
Reputation: 106543
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]]
Upvotes: 1