Reputation: 27
So I have a dictionary
dict = {"key1" : {"key2": {"key3": 4 } } }
and a list of keys of a hierarchy
list = ['key1","key2","abc"]
Now I want to check if the list of keys exists within the dict retaining that hierarchy, if not maybe return a None. So in the above case I should return a None instead of an Error
The solution must be dynamic for any dict and list of keys for that dict, not a static one involving manually checking it. I've been working on it for a few hours now but couldn't find a solution, any help would be greatly appreciated
Upvotes: 0
Views: 1241
Reputation: 6657
Answer of Mark Meyer was selected as the right one, but it will fail for the following input:
d = {"key1" : {"key2": {"key3": 4 } }, "key5": {"key6": 1}}
findindict(d, ["key1","key2", "key3", "key6"])
In [86]: def findindict(d, l):
...: try:
...: return reduce(lambda current_dict, key: current_dict[key], l, d)
...: except KeyError:
...: return None
...:
In [87]: d = {"key1" : {"key2": {"key3": 4 } }, "key5": {"key6": 1}}
In [88]: findindict(d, ["key1","key2", "key3", "key6"])
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-88-42fa73bf849f> in <module>
----> 1 findindict(d, ["key1","key2", "key3", "key6"])
<ipython-input-86-c54e471e6146> in findindict(d, l)
1 def findindict(d, l):
2 try:
----> 3 return reduce(lambda current_dict, key: current_dict[key], l, d)
4 except KeyError:
5 return None
<ipython-input-86-c54e471e6146> in <lambda>(current_dict, key)
1 def findindict(d, l):
2 try:
----> 3 return reduce(lambda current_dict, key: current_dict[key], l, d)
4 except KeyError:
5 return None
TypeError: 'int' object is not subscriptable
Catching of that TypeError will not help - it will not traverse all the keys inside nested dict.
It's failing because it will not check all the keys. So let's create the function, which will get all the keys:
def get_all_keys(d):
dict_keys = list(d.keys()) if isinstance(d, dict) else []
keys = dict_keys[:]
for key in dict_keys:
keys += get_all_keys(d[key])
return keys
So your target function will look like:
def all_keys_presented_in_dict(d, keys):
return not bool(set(keys) - set(get_all_keys(d)))
If later you would love to check if at least some (not all) of the keys are presented in target dictionary - you can easily do that, because you have access to all keys in the nested dictionary.
UPDATE
As @MarkMeyer has noticed - OP has asked about "existence" of keys and "retaining of hierarchy". So my solution should be updated to check if one list (of target keys) is subsequence of another list (of all keys, retaining hierarchy).
So let's add the function to check that:
def is_subsequence(subsequence, target):
subsequence_length = len(subsequence)
for i in range(len(target) - subsequence_length + 1):
if target[i:i + subsequence_length] == subsequence:
return True
return False
def all_keys_presented_in_dict_with_hierarchy_being_retained(d, keys):
return is_subsequence(keys, get_all_keys(d))
For example:
In [26]: all_keys_presented_in_dict_with_hierarchy_being_retained(d, ["key2", "key3"])
Out[26]: True
In [27]: all_keys_presented_in_dict_with_hierarchy_being_retained(d, ["key2"])
Out[27]: True
In [28]: all_keys_presented_in_dict_with_hierarchy_being_retained(d, ["key2", "key1"])
Out[28]: False
Upvotes: -1
Reputation: 92440
You can use functools.reduce()
for this, you just need to anticipate the KeyError
. For example:
from functools import reduce
d = {"key1" : {"key2": {"key3": 4 } } }
def findindict(d, l):
try:
return reduce(lambda current_dict, key: current_dict[key], l, d)
except (KeyError, TypeError):
return None
findindict(d, ["key1","key2", "key3"])
# 4
findindict(d, ["key1","key2", "abc"])
# None
findindict(d, ["key1","key2", "key3", "key6"])
#None
Upvotes: 1
Reputation: 66
Don't use dict and list as variable name. If you want to return the structure from the given key:
def NextedKeyExists(dictobj, key):
if key in dictobj:
return dictobj[key]
for v in dictobj.values():
if isinstance(v, dict):
r = NextedKeyExists(v, key)
if r != None:
return r
return None
usage:
dictobj = {"key1" : {"key2": {"key3": 4 } } }
listobj = ["key1", "key2", "abc"]
for l in listobj:
print (l, " -> ", NextedKeyExists(dictobj, l))
outputs:
key1 -> {'key2': {'key3': 4}}
key2 -> {'key3': 4}
abc -> None
Upvotes: 1
Reputation: 21
I think this little recursive function should do it.
def find_path(nested_structure, path):
top = path[0]
if top not in nested_structure.keys():
return None
else:
value = nested_structure[top]
if isinstance(value, dict) and len(path)>0:
return find_path(nested_structure[top], path[1:])
else:
return value
structure = {"key1" : {"key2": {"key3": 4 }, "key2b": 42 } }
print(find_path(structure, ["key1","key2","abc"])) # -> None
print(find_path(structure, ["key1","key2","key3"])) # -> 4
Upvotes: 2
Reputation: 15488
You can try like this don't use dict as variable name:
mydict = {"key1" : {"key2": {"key3": 4 } } }
mykeys = []
def walk_dict(d,depth=0):
for k,v in sorted(d.items(),key=lambda x: x[0]):
if isinstance(v, dict):
mykeys.append(k)
walk_dict(v,depth+1)
else:
mykeys.append(k)
def rec_check_keys(keylist):
thekeys = walk_dict(mydict)
flag = True
for x in keylist:
if not x in thekeys:
flag = False
if flag == False:
return None
else:
return True
Upvotes: 0