Message Boards Message Boards


Meal Planning in the Wolfram Language

Posted 1 year ago
1 Reply
6 Total Likes

enter image description here

The Problem

As a software developer, I've always loved the challenge of taking an aspect of life and trying to streamline it with code. There are so many things we do day-to-day that can be helped by some code to "grease the wheels".

Take meal planning, for example. There are a variety of tasks here: Thinking of meal ideas, aggregating the list of ingredients, checking what ingredients you already have to produce a final shopping list, grocery shopping, and then eventually making those meals, at which point one typically needs the recipes handy.

When I do the above things by hand, I find them cumbersome. First of all, as I'm sitting there with pen and paper, I struggle to conjure up meal ideas. Looking through recipe books (etc) can be mildly helpful, but it's still slow going. Once I have some recipes picked out, I find it tedious aggregating the ingredients -- it all feels like something that could be done automatically, so why am I doing it by hand? When grocery shopping, I feel disorganized. It's hard to mentally sort the grocery list into where things are in the store, so I will often miss an item or two and have to retrace my steps to get them. Or, I will have forgotten what isle things are in, so I wander around aimlessly. It's all so inefficient! And finally, when I make the recipes, I find it unfortunate that I have to go find a recipe book and re-find what page the recipe is on.

Fortunately, it's not that hard to solve all of the above things with some code, and the Wolfram Language is ideal for these sorts of things.

Choosing Meals

There are a variety of ways to do this, some of them very simple, some of them very complex. For instance, you could write an algorithm to create a meal plan automatically that balances nutritional elements. What I've done to get the ball rolling is simply to have a function that "rolls the dice" and presents three random recipes to me. If I like any of them, I click on the recipe and it adds it to my meal plan. Otherwise, I "roll the dice" again. This may sound somewhat inefficient, but it often takes less than 30 seconds for me to create a nice meal plan.

I love how easy the Wolfram Language makes simple UI development. I have a utility function called SelectUI which is just like the language's Select function, but gives you buttons to select the things you want. With a function like that, building a little UI to select recipes is very easy. In the image below, the yellow buttons are items that have been selected.

Choosing Meals

Creating a Shopping List

Now that we have a list of recipes, we want to create a shopping list. To make this possible, we need a symbolic representation of a recipe and its ingredients. Here's the representation I chose:

    "Name" -> "Spaghetti",
    "Label" -> "Spaghetti",
    "Ingredients" -> {
            "FoodQuantity" -> FoodQuantity[
                "Food" -> Entity["Food", "SpaghettiNoodles"],
                "Quantity" -> Quantity[450, "Grams"]
            "RawString" -> "450 g of spaghetti noodles"
            "FoodQuantity" -> FoodQuantity[
                "Food" -> Entity["Food", "PastaSauce"],
                "Quantity" -> Quantity[800, "Milliliters"]
            "RawString" -> "800 ml pasta sauce"
            "FoodQuantity" -> FoodQuantity[
                "Food" -> Entity["Food", "GroundBeef"],
                "Quantity" -> Quantity[1, "Pounds"]
            "RawString" -> "1 lb ground beef"
    "IngredientsString" -> "450 g of spaghetti noodles\n800 ml pasta sauce\n1 lb ground beef",
    "SupperQ" -> True,
    "LunchQ" -> False,
    "SideQ" -> False,
    "SaladQ" -> False,
    "Instructions" -> ""

You'll notice that my symbolic representation is quite like a nested Asssociations, except that I'm using symbolic heads. Nested associations would also work well here. To make getting and setting keys easy, I have functions Gett and Sett which allow you to get and set keys on not only Associations but also lists of key/value pairs and headed expressions with key/value pairs.

Once we have this symbolic representation, we can create a first draft of our shopping list by taking the Union of the ingredients of each of our recipes.

The next step takes a bit more effort: If two recipes make use of ground beef, for example, then we'd like to replace the two FoodQuantity expressions with a single FoodQuantity that combines their Quantities. This is easy when both food quantities are a mass, or both are a volume, because we can just do:

Quantity[1, "Pounds"] + Quantity[500, "Grams"]

It's trickier if one of the quantities is a volume and the other is a mass. In that case, we need the density of the food. But this is another case where the Wolfram Language shines, because we can grab that data from Wolfram Alpha:

foodName = "Pasta";
WolframAlpha["density of " <> foodName, {{"Result", 1}, "Output"}]

Pruning the Shopping List

Now that we have a shopping list, we need to prune it. ie. There's a good chance we have at least half of the things already in our kitchen.

To make this as efficient as possible, we should by default select all foods that have < 50% chance of being in the kitchen, and de-select all foods with > 50% chance. For each food, I've tagged it with a key/value pair called "Probability", making the default select easy to decide.

It's also somewhat helpful to sort the list of foods by the likelihood we have them, so that as you scroll down the list, you can pay very close attention to the things at the top (which are also selected by default) and can quickly scan over the things at the bottom of the list, which you almost certainly don't need to buy. (salt, pepper, flour, etc.)

Once again, we can whip up a UI in no time using a function like SelectUI to make it easy to prune the shopping list:

Pruning the shopping list

Producing the Final Shopping List

We're ready to go shopping -- almost. Before we produce a PDF of our shopping list, we should first sort the foods by their location in the grocery store, and to label them in the shopping list by their location in the store. This is helpful for two reasons:

  • It makes it very fast to flow through the store -- just look at what item is next in your list, and if you can't remember where it is, pay attention to the store location printed beside it.

  • Avoids missing items and having to circle back for them.

Implementing this function took some elbow grease, because it requires knowing where foods are in the store. I took a pen and a notebook and spent about an hour and a half cataloging my local store. For each side of each isle, I divided it into sub-sections A/B/C/D/E/F/G/H, and for each of those sub-sections, I wrote down the food items that I've ever bought in the last 10 years. When I got home, I wrote a bit of parsing code using functions like StringSplit to turn those notes into an expression, after which it was easy to update my list of food expressions with their store locations.

Something that can be useful to have (such as when doing the above) is a way to take a food name like "ground beef" or "hamburger" and turn it into its normalized Entity["Food", ...] expression. The Wolfram Language's integration with Wolfram Alpha also makes this easy. Because I'm a bit of a low latency freak and enjoy writing parsing code, I have my own local linguistics and parser so that I can parse something like "1 lb of ground beef" in about 15 milliseconds. Maybe some day the Wolfram Language will support local parsing of things like this to give fast performance.

I have also labeled all of my food items with their "type", such as "Vegetable", "Fruit", "FrozenVegetable", "DairyProduct", "Oil", etc. etc. That way, when I add new food items in the future where I don't yet have a grocery store location for it, I can specify its type, and then the code can look at store locations of foods with the same type and use that to infer an intelligent default for where it may be in the store. Those store locations are printed with a question mark after them on my grocery list so that I'm aware it's just a guess, and I can note the exact location to add to my food items when I get home.

The order of the shopping list follows the order of store areas that I've given the code. It works well, but I'm tempted to geometrically model the store locations and use the FindShortestTour function to produce even more optimized tours through the store.

We now have our final shopping list. Using Wolfram Language's CreateDocument function, we can dynamically generate a notebook:

Final shopping list

The notebook I generate has an additional section for each recipe where I give its ingredients and instructions. That way, when I print it out, I can place the instructions beside the fridge where they are handy each evening, avoiding having to look things up in a recipe book.

My wife has complained that I'm wasting paper, so now I also use CloudDeploy to put the generated notebook in the cloud at a dependable URL so that she can click on a link in her bookmarks bar to see the recipe instructions. Maybe some day soon I'll kick my habit of printing out the recipes!

Grocery Shopping

With my shopping list in hand, I head to the grocery store. I had never really enjoyed grocery shopping in the past, but now I find it exhilarating. How fast can I flow through the store and get back to the car? It's not uncommon for me to do it in 10 minutes now, where it might have taken me 25 minutes before.

It's also fun to keep stats on how quickly I can do the whole stack of tasks: Planning meals, grocery shopping, and putting the food away. My record so far is about 35 minutes. When life is this efficient, I find it much more fun and energizing.

Video Demonstration

Please follow this link for a Video Demonstration

enter image description here - Congratulations! This post is now a Staff Pick as distinguished by a badge on your profile! Thank you, keep it coming!

Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
or Discard

Group Abstract Group Abstract