How to get keys from nested dictionary in a pythonic way

Question:

I have some items in a nested dictionary and I need to match if any word in a sentence matches the values in the dictionary. If it does, it returns all the keys from the nested list.

What I have tried so far:

animals = {
  'vertebrates': {
    'warm_blooded': {
      'mammals': ['bear', 'tiger', 'lion'],
      'birds': ['eagle', 'ostrich', 'duck']
    },
    'cold_blooded': {
      'reptiles': ['turtle', 'crocodile'],
      'amphibians': ['frog', 'toad']
    }
  }
}

line = 'lions live in the savanna.' 

for key1, value1 in animals.items():
  for key2, value2 in value1.items():
    for key3, value3 in value2.items():
      if any(word in line for word in value3):
        print ([key1, key2, key3])

>>> ['vertebrates', 'warm_blooded', 'mammals']

Currently it does what I need. What I want to know if there’s a way to re-write this code in a more pythonic (elegant) way as this for loop might get longer if there’s more levels in the dictionary to to tranverse through.

Asked By: Cryssie

||

Answers:

Could make a recursive function that keeps track of the path, and prints the path when an animal is found in line.

def search_animals_rec(animals, line, path):
    for k, v in animals.items():
        if isinstance(v, dict):
            search_animals_rec(v, line, path + [k])
        elif isinstance(v, list):
            for animal in v:
                if animal in line:
                    print(path + [k])

search_animals_rec(animals, line, [])

Or using any():

def search_animals_rec(animals, line, path):
    for k, v in animals.items():
        if isinstance(v, dict):
            search_animals_rec(v, line, path + [k])
        elif isinstance(v, list):
            if any(animal in line for animal in v):
                print(path + [k])

Output:

['vertebrates', 'warm_blooded', 'mammals']

Note: The above obviously doesn’t handle all edge cases, but it shows how you could approach a recursive brute force solution. A more efficient solution would be build a reverse index as pointed out in the comments.

Answered By: RoadRunner

Here is a simple method using recursion which keeps track of keys along the way. This should illustrate how you would do this for arbitrary lengths.

def key_match(data, to_match, found):
    for k, v in data.items():
        if isinstance(v, dict):
            key_match(v, to_match, found + [k])
        else:
            if any(word in line for word in v):
                print(found + [k])

found = []
key_match(animals, line, found)

['vertebrates', 'warm_blooded', 'mammals']
Answered By: gold_cy

I like the flatten-dict module, might come in handy in a few operations :

from flatten_dict import flatten
flat = flatten(animals)
print(flat)

{('vertebrates', 'warm_blooded', 'mammals'): ['bear', 'tiger', 'lion'],
 ('vertebrates', 'warm_blooded', 'birds'): ['eagle', 'ostrich', 'duck'],
 ('vertebrates', 'cold_blooded', 'reptiles'): ['turtle', 'crocodile'],
 ('vertebrates', 'cold_blooded', 'amphibians'): ['frog', 'toad']}

You won’t find lions in any of the values, but take away the ‘s’ and you’ll find lion:

line = 'lion live in the savanna.'
#used a set to avoid duplication
words = set(line.strip('.').split())
print(words)
{'in', 'lion', 'live', 'savanna', 'the'}


[key for word in words for key,value in flat.items() if word in value ]

[('vertebrates', 'warm_blooded', 'mammals')]
Answered By: sammywemmy
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.