pythoncs50

How do I search through a dict using user's input as a key inside a nested dict?


Working on my final project for cs50 Introduction to programming with Python. Trying to implement a programm with a few functions, such as: calculating basal metabolic rate, formatting a list of products, printing a nutritional value of a user specified product and suggesting/sorting products by their value depending on a macronutrient specified by user.

Having trouble with the latter function.

I have a two-level deep dict of different products with their nutritional value:

products = {
    "almonds": {"protein": 6.0, "fat": 14.2, "carbs": 6.1, "fiber": 3.5, "calories": 164},
    "banana": {"protein": 1.3, "fat": 0.4, "carbs": 27.0, "fiber": 3.1, "calories": 105},
    "buckwheat": {"protein": 5.7, "fat": 1.0, "carbs": 34.0, "fiber": 4.5, "calories": 155},
    "ground beef": {"protein": 24.0, "fat": 13, "carbs": 0, "fiber": 0, "calories": 218}
}

I want to implement a function which asks the user for a macronutrient (protein, fat, carbs, fiber or calories - all are the keys for a nested dict), searches through the products dict and then outputs a top-three products, based on their nutritional value specified by user.

I am having trouble with sorting the dict by it's value (as I see it). Thought about storing the value as a nested list:

products = {"banana": [1.3, 0.4, 27.0, 3.1, 105]}

So I can call a value by:

product = input("Name of a product: ").lower().strip()
macro = input("Name of a macronutrient: ").lower().strip()
# using macro variable and if statements to specify the index of a list
if macro == "protein":
    products.keys(product)[0] # running into a problem when calling product here

But the products dict seems kind of cryptic anyway, so I would like to avoid this option.

As of now I have the following function:

def _get_nested_val(data, *args):
    if args and data:
        element  = args[0]
        if element:
            value = data.get(element)
            return value if len(args) == 1 else _get_nested_val(value, *args[1:])

I call this function in main() for showing the user a nutritional value of a specific product:

    elif initial == "show specifics":
            product = input("Enter a name of the product: ").lower().strip()
            call = input("Enter a macronutrient: ").lower().strip()
            if call == "protein" or call == "fat" or call == "carbs" or call == "fiber":
                print(_get_nested_val(products, product, call), "grams of", call, "in", product.capitalize())
            elif call == "calories":
                print(_get_nested_val(products, product, call), "calories in", product.capitalize())

I was wondering if I can use _get_nested_val() function to sort and output a few products, specified by user's input.

def main():
    initial = input("What do you want the programm to do? ").lower().strip()
    ... # other functions are called here, not relevant to the current problem
    elif initial == "suggest":
        suggest()

def suggest():
    req = input("Based on what macronutrient would you like the products to be suggested? ")
    if request == "protein":
    ...

### I would like to see the programm running like something this:
# $ What do you want the programm to do? suggest
# $ Based on what macronutrient would you like the products to be suggested? protein
# The following products are high in protein: Beef, Chicken breast, ...

I have checked a lot of other questions on Stack Overflow but fail to get my programm to work as I want it to.


Solution

  • Your starting point should be to develop a class that supports the nutritional values. That will make your code more concise and easier to maintain.

    The problem you're having (as I understand it) is that you want user input of a macronutrient (e.g., fat, carbs) then sort your products dictionary based on the macronutrient value.

    If that's the case then:

    from collections import UserDict
    
    class Nutrients(UserDict):
        MN = ["protein", "fat", "carbs", "fibre", "calories"]
    
        def __init__(self, *args):
            super().__init__()
            assert len(args) == len(Nutrients.MN)
            self.data.update(zip(Nutrients.MN, args))
    
    products: dict[str, Nutrients] = {
        "almonds": Nutrients(6.0, 14.2, 6.1, 3.5, 164),
        "banana": Nutrients(1.3, 0.4, 27.0, 3.1, 105),
        "buckwheat": Nutrients(5.7, 1.0, 34.0, 4.5, 155),
        "ground beef": Nutrients(24.0, 13, 0, 0, 218),
    }
    
    mn = input("Input macronutrient: ")
    
    if mn in Nutrients.MN:
        print(f"The following are high in {mn}:")
        for p, _ in sorted(products.items(), key=lambda t: t[1][mn], reverse=True):
            print(f"\t{p.capitalize()}")
    

    Terminal:

    Input macronutrient: protein
    The following are high in protein:
            Ground beef
            Almonds
            Buckwheat
            Banana