J'essaie d'écrire une fonction très simple pour rechercher récursivement dans un dictionnaire éventuellement imbriqué (dans les cas les plus extrêmes de dix niveaux de profondeur) Python et renvoyer la première valeur qu'il trouve à partir de la clé donnée.
Je ne comprends pas pourquoi mon code ne fonctionne pas pour les dictionnaires imbriqués.
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
_finditem(v, key)
print _finditem({"B":{"A":2}},"A")
Il renvoie None
.
Cela fonctionne, cependant, pour _finditem({"B":1,"A":2},"A")
, retournant 2
.
Je suis sûr que c'est une simple erreur mais je ne la trouve pas. J'ai l'impression qu'il pourrait déjà y avoir quelque chose pour cela dans la bibliothèque standard ou collections
, mais je ne le trouve pas non plus.
lorsque vous recurse, vous devez return
le résultat de _finditem
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
return _finditem(v, key) #added return statement
Pour corriger l'algorithme réel, vous devez vous rendre compte que _finditem
Renvoie None
s'il n'a rien trouvé, vous devez donc le vérifier explicitement pour empêcher un retour précoce:
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
item = _finditem(v, key)
if item is not None:
return item
Bien sûr, cela échouera si vous avez des valeurs None
dans l'un de vos dictionnaires. Dans ce cas, vous pouvez mettre en place une sentinelle object()
pour cette fonction et renvoyer cela dans le cas où vous ne trouverez rien - Ensuite, vous pouvez vérifier par le sentinel
pour savoir si vous avez trouvé quelque chose ou non.
Voici une fonction qui recherche un dictionnaire qui contient à la fois des dictionnaires et des listes imbriqués. Il crée une liste des valeurs des résultats.
def get_recursively(search_dict, field):
"""
Takes a dict with nested lists and dicts,
and searches all dicts for a key of the field
provided.
"""
fields_found = []
for key, value in search_dict.iteritems():
if key == field:
fields_found.append(value)
Elif isinstance(value, dict):
results = get_recursively(value, field)
for result in results:
fields_found.append(result)
Elif isinstance(value, list):
for item in value:
if isinstance(item, dict):
more_results = get_recursively(item, field)
for another_result in more_results:
fields_found.append(another_result)
return fields_found
Voici un moyen de le faire en utilisant une "pile" et le modèle "pile d'itérateurs" (crédits à Gareth Rees):
def search(d, key, default=None):
"""Return a value corresponding to the specified key in the (possibly
nested) dictionary d. If there is no item with that key, return
default.
"""
stack = [iter(d.items())]
while stack:
for k, v in stack[-1]:
if isinstance(v, dict):
stack.append(iter(v.items()))
break
Elif k == key:
return v
else:
stack.pop()
return default
La print(search({"B": {"A": 2}}, "A"))
afficherait 2
.
Je n'ai pas pu ajouter de commentaire à la solution acceptée proposée par @mgilston par manque de réputation. La solution ne fonctionne pas si la clé recherchée se trouve dans une liste.
Le fait de parcourir les éléments des listes et d'appeler la fonction récursive devrait étendre la fonctionnalité pour rechercher des éléments dans des listes imbriquées:
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
item = _finditem(v, key)
if item is not None:
return item
Elif isinstance(v,list):
for list_item in v:
item = _finditem(list_item, key)
if item is not None:
return item
print(_finditem({"C": {"B": [{"A":2}]}}, "A"))
J'ai dû créer une version générale qui trouve une clé à spécification unique (un dictionnaire minimal qui spécifie le chemin d'accès à la valeur souhaitée) dans un dictionnaire qui contient plusieurs dictionnaires et listes imbriqués.
Pour l'exemple ci-dessous, un dictionnaire cible est créé pour la recherche et la clé est créée avec le caractère générique "???". Lorsqu'il est exécuté, il renvoie la valeur "D"
def lfind(query_list:List, target_list:List, targ_str:str = "???"):
for tval in target_list:
#print("lfind: tval = {}, query_list[0] = {}".format(tval, query_list[0]))
if isinstance(tval, dict):
val = dfind(query_list[0], tval, targ_str)
if val:
return val
Elif tval == query_list[0]:
return tval
def dfind(query_dict:Dict, target_dict:Dict, targ_str:str = "???"):
for key, qval in query_dict.items():
tval = target_dict[key]
#print("dfind: key = {}, qval = {}, tval = {}".format(key, qval, tval))
if isinstance(qval, dict):
val = dfind(qval, tval, targ_str)
if val:
return val
Elif isinstance(qval, list):
return lfind(qval, tval, targ_str)
else:
if qval == targ_str:
return tval
if qval != tval:
break
def find(target_dict:Dict, query_dict:Dict):
result = dfind(query_dict, target_dict)
return result
target_dict = {"A":[
{"key1":"A", "key2":{"key3": "B"}},
{"key1":"C", "key2":{"key3": "D"}}]
}
query_dict = {"A":[{"key1":"C", "key2":{"key3": "???"}}]}
result = find(target_dict, query_dict)
print("result = {}".format(result))