Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing elements based on nested dictionary values

I have a complex nested dictionary structure and I need to remove elements based on the values in a nested dictionary. My dictionary looks like this:

my_dict = {
    'item1': {'name': 'Apple', 'price': 1.0, 'category': {'id': 1, 'name': 'Fruit'}},
    'item2': {'name': 'Banana', 'price': 0.5, 'category': {'id': 1, 'name': 'Fruit'}},
    'item3': {'name': 'Carrot', 'price': 0.75, 'category': {'id': 2, 'name': 'Vegetable'}},
    'item4': {'name': 'Broccoli', 'price': 1.5, 'category': {'id': 2, 'name': 'Vegetable'}}
}

I want to filter this dictionary to only include items belonging to the 'Fruit' category. I tried the following code:

new_dict = {}
for key, value in my_dict.items():
    if value['category']['name'] == 'Fruit':
        new_dict[key] = value
print(new_dict)

This works, but I'm wondering if there's a more concise or Pythonic way to achieve this, perhaps using dictionary comprehension or a filtering function like filter().


2 Answers

As a dictionary comprehension

A dictionary comprehension can be used to create dictionaries from arbitrary key and value expressions.

new_dict2 = {
    key: value
    for key, value in my_dict.items()
    if value['category']['name'] == 'Fruit'
}
new_dict2 == new_dict
# True

Using filter()

The filter() function is used to:

Construct an iterator from those elements of iterable for which function is true.

The dict.items() returns an iterable where each element is a tuple of length 2. We can supply each item to a lambda function, where item[0] will be the key and item[1] the value. filter() returns an iterator of the tuples which match the condition. We can wrap this in dict() to get a dictionary (in the same way that dict([("key1", "value1"), ("key2", "value2")]) returns {'key1': 'value1', 'key2': 'value2'}).

new_dict3 = dict(
    filter(
        lambda item: item[1]['category']['name'] == 'Fruit',
        my_dict.items()
    )
)
new_dict3 == new_dict
# True

Most Pythonic way

Achieving the nebulous goal of Pythonicness (Pythonicity?) is always somewhat subjective. I think a dictionary comprehension is clean and neat but it can be hard to see what it's doing, especially if the dict is deeply nested or the condition is complex. It's probably clearest if you wrap it in an appropriately-named function so you can see what's going on. I've added type annotations for clarity:

def find_fruit(d: dict[str, dict]) -> dict[str, dict]:

    def is_fruit(key: str, value: dict) -> bool:
        return value["category"]["name"] == "Fruit"

    return {key: value for key, value in d.items() if is_fruit(key, value)}

fruit_dict = find_fruit(my_dict)
new_dict == fruit_dict
# True

This is fundamentally the same as the first approach but easier on the eyes.

like image 196
SamR Avatar answered Jul 18 '26 12:07

SamR


You could use a dictionary comprehension which would be more concise but, arguably, harder to read:

my_dict = {
    'item1': {'name': 'Apple', 'price': 1.0, 'category': {'id': 1, 'name': 'Fruit'}},
    'item2': {'name': 'Banana', 'price': 0.5, 'category': {'id': 1, 'name': 'Fruit'}},
    'item3': {'name': 'Carrot', 'price': 0.75, 'category': {'id': 2, 'name': 'Vegetable'}},
    'item4': {'name': 'Broccoli', 'price': 1.5, 'category': {'id': 2, 'name': 'Vegetable'}}
}

new_dict = {k: v for (k, v) in my_dict.items() if v["category"]["name"] == "Fruit"}

print(new_dict)
like image 44
Ramrab Avatar answered Jul 18 '26 13:07

Ramrab