web-dev-qa-db-fra.com

Recherche récursive d'une clé dans un dictionnaire

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.

26
Fredrick Brennan

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.

48
mgilson

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
19
Becca Petrin

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.

5
alecxe

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"))

0
Pablo

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))
0
isometric