Back to Contents

Project 2: Counting Calories

A popular way of failing to lose weight is to count calories. We are going to write a program to calculate and track the food and calorie intake of one or more people. For this, we will need to store information in files, and load information from others. For example, here is a very short list of foods and their calorie counts:

Carrots(Cooked) 100 109
Carrots(Raw) 100 111
Peas 454 350

The first column is the food name, the second the number of grams in question, and the third the number of calories in that number of grams. The columns are separated by spaces (and so there can be no space in the food name). This is the most common source data for calories, easy to type in, and easy to collate data from multiple sources. You can see that the Peas figure is for 454g, which is approximately one pound. A file containing the foods eaten in a single day will look like this:

Carrots 160
Peas 110

The first column is the food, the second the number of grams. The calories can be calculated by reference to the calories data file. We will also store a file containing the history of weights:

01-01-2020 84.3
03-01-2020 84.1

Here 84.3kg for 1st January and 84.1kg for 3rd January. Each person’s files will be in its own directory so we might have a structure like this:

image

You can build this structure yourself by typing in the data shown above (and making some up), or by downloading it from the book’s website. Here is a function to read a table from a file:

image

You can see that we read it as a dictionary. The first column of the file makes the keys, and the values are lists of the rest of the zero or more items in each row. Everything is treated as a string, whether it represents a number or not. We have assumed the file is well-formed, and have not checked for errors. One of the exercises will be to remedy this. Here is the table of calories.txt:

{'Carrots(Cooked)': ['100', '109'],
 'Carrots(Raw)': ['100', '111'],
 'Peas': ['100', '350']}

We can write a function to print the foods out in a nicer format:

image

Here is the result:

Python
>>> list_foods()
Carrots(Cooked) 100 109 
Carrots(Raw) 100 111 
Peas 100 350 

The function os.path.join which we can access by using import os can be used to construct the full path to a file given its directory and file name. For example, os.path.join(’robert’, ’weight.txt’) might produce ’robert/weight.txt’ (the exact result will depend on your system). We can use this to list the foods eaten on a particular day:

image

Question 1 Write a function list_weights to list the contents of the weight.txt file, given the name of the user.

Question 2 Write a function list_dates to list the dates for which we have calorie data for a given user. The function os.listdir(’robert’), for example, will produce an (unsorted) list of the files in the directory robert.

Now we can write a function to look up the number of calories for a given food in calories.txt:

image

We have used dictionary lookup to locate the weight and calories for the food, and then slicing to extract them from the list.

Question 3 Write a function lookup_weight which, given a name and a date, prints the weight at that date, if available.

To calculate the total calories for a given date, we must combine data from the user’s food records for that date with the calorie data:

image

Question 4 Write a function new_user which, given a name, makes a new directory for that person, and creates an empty weight.txt file. You will need the function os.mkdir which, given a name, makes a directory.

So far, we have been able to treat dates like any other kind of string, without regard to their meaning. However, to add data to the correct file, we would like the user to be able to avoid typing the date – the data should be added to the file with today’s date. We use a function from the datetime module:

image

The structure d contains today’s time and date. We extract the day, month, and year, making sure to format the day and month to two character each (i.e. to include leading zeroes). Now we can use our new date_today function to write a functions to add data for today, using the append file mode:

image

The program will be run from the command line by writing python cals.py followed by the command and any extra data required. Here are the commands:

image

A rather convoluted construction implements this, dispatching to the functions we have already written. There are, as you might expect, Python libraries to deal with this sort of task and, if our program became any more complicated, we would switch to one of them.

image

Question 5 In many of our functions, we have not checked for errors. For example, do files exist? Are they in the correct format? Is the command line correct? Try to enumerate all the possible errors requiring detection. Pick one or two functions and fix them to deal with such errors.

A standard data format

We have been using our own ad-hoc representation for our data files; but there is an longstanding industry standard: the CSV or Comma-Separated Values format. This is rather like our own format, but adds a line at the top with column titles, and allows entries to have spaces inside them, if they are surrounded by quotation marks:

Food,Weight,Calories
"Carrots, Cooked",100,109
"Carrots, Raw",100,111
"Peas",100,350

The advantage of using this standard format, is that we can replace our own table handling functions with the Python CSV library. In addition, we can use the CSV files created by our program to export data to other programs, for example a spreadsheet. Here is our calories CSV file, imported into Microsoft Excel, for example:

image

How do we convert our cals.py program into the new csvcals.py program? First we import the csv module and modify our table_of_file function:

image

Now it is very easy to modify our other functions to change the filenames to .csv. Because we have modified table_of_file function, all our table-reading and processing functions work just fine:

image

The places where we do need to make more significant changes are in the functions which create new files. We must add the column headings:

image

We must put quotation marks around the food name, and add the CSV header if the file does not already exist, using the function os.path.exists, which checks if a file or directory already exists:

image

This completes the modifications.

Question 6 Complete the conversion of our cals.py program into csvcals.py, and test it.

Question 7 Locate the Python CSV module documentation online, and use it to alter our csvcals.py program to write files using functions provided by that module, as well as read using them.