Problem 12
Pizza
Read a pizza menu from a CSV file and print it as a table, checking the command line first.
Background
Perhaps the most popular place for pizza in Harvard Square is Pinocchio’s Pizza & Subs, aka Noch’s, known for its Sicilian pizza, which is “a deep-dish or thick-crust pizza.”
Students tend to buy pizza by the slice, but Pinocchio’s also has
whole pizzas on its menu. Here is the menu for its Sicilian pizzas as a
CSV file, sicilian.csv:
Sicilian Pizza,Small,Large
Cheese,$25.50,$39.95
1 item,$27.50,$41.95
2 items,$29.50,$43.95
3 items,$31.50,$45.95
Special,$33.50,$47.95
There’s a second file, regular.csv, for the regular pizzas. A CSV
file (short for “comma-separated values”) has one row per line, with a
comma between each column. The first row holds the column names.
A CSV file isn’t the most customer-friendly format to look at. A table
drawn in ASCII art would be nicer. You’ll write a program that reads
one of these files and prints it that way, using a package called
tabulate to do the drawing.
Here’s a quick look at the program you’re about to write. Pick a command and see what it prints.
The demo has both menus built in. Your program will read the file from
disk instead, which is where open and the csv module come in.
Getting Started
Log into cs50.dev, click on your terminal window, and run:
cd
mkdir pizza
cd pizza
code pizza.py
That creates a new folder called pizza, moves into it, and opens a
new, empty file called pizza.py for you to edit.
Next, create and activate a virtual environment, the same way you did
for Emojize, so the tabulate package stays scoped to this project:
python -m venv myvenv
source myvenv/bin/activate
You’ll know it worked because your prompt starts showing (myvenv). If
you come back in a new terminal, cd into pizza and run source
myvenv/bin/activate again before anything else.
Install tabulate, which does the table drawing for you:
pip install tabulate
Then download the two menus into your pizza folder:
wget https://learn.porttack.com/assets/data/cs50psets/pizza/regular.csv
wget https://learn.porttack.com/assets/data/cs50psets/pizza/sicilian.csv
Run ls to make sure both files are there. Use cat regular.csv to
look inside one.
Starter Code
Paste this into pizza.py to start from. check_args and
format_table are stubbed out with pass and doctest examples; run
python3 -m doctest pizza.py from inside your pizza folder to check
them. They’ll fail until you replace pass with real code. (The
doctests call tabulate, so your virtual environment needs to be
active.)
main and read_rows are left as comments on purpose. Reading a file
and exiting with a message are things a docstring can’t check, so
check50 does that part.
# Student Initials:
"""
Pizza
Slug: porttack/cs50/problems/py/pizza
Doctests: python3 -m doctest pizza.py
"""
import csv
import sys
from tabulate import tabulate
def main():
# Call check_args with sys.argv. If it returns a message, exit with
# that message using sys.exit. Otherwise read the rows from the
# file named in sys.argv[1] with read_rows. If the file doesn't
# exist, exit with "File does not exist". Then print the result of
# format_table.
pass
def check_args(argv):
"""
Takes the list of command-line arguments, program name included.
Returns a message saying what is wrong with it, or None if it is
fine.
>>> check_args(["pizza.py"])
'Too few command-line arguments'
>>> check_args(["pizza.py", "regular.csv", "sicilian.csv"])
'Too many command-line arguments'
>>> check_args(["pizza.py", "menu.txt"])
'Not a CSV file'
>>> print(check_args(["pizza.py", "regular.csv"]))
None
"""
pass
def read_rows(filename):
# Open the file and return its rows as a list of lists, header row
# first. The csv module's reader will split each line for you.
pass
def format_table(rows):
"""
Takes a list of rows, header row first, and returns the table as a
string, drawn in the "grid" format.
>>> print(format_table([["Drink", "Price"], ["Soda", "$2.00"]]))
+---------+---------+
| Drink | Price |
+=========+=========+
| Soda | $2.00 |
+---------+---------+
"""
pass
if __name__ == "__main__":
main()
Specification
Implement a program, in pizza.py, that expects exactly one
command-line argument, the name (or path) of a CSV file, and outputs
that file’s contents as an ASCII art table.
- Use
tabulatewith itsgridtable format. The first row of the file is the header. - Exit via
sys.exitwith the messageToo few command-line argumentsif the user does not specify exactly one command-line argument, orToo many command-line argumentsif they specify more than one. - Exit via
sys.exitwithNot a CSV fileif the specified file’s name does not end in.csv. - Exit via
sys.exitwithFile does not existif the specified file does not exist. - Your program should work with any CSV file in this shape, not just the two pizza menus.
Usage
Your program should behave like the examples below.
$ python pizza.py Too few command-line arguments $ python pizza.py regular.csv sicilian.csv Too many command-line arguments $ python pizza.py sicilian.txt Not a CSV file $ python pizza.py invalid_file.csv File does not exist $ python pizza.py regular.csv +-----------------+---------+---------+ | Regular Pizza | Small | Large | +=================+=========+=========+ | Cheese | $13.50 | $18.95 | +-----------------+---------+---------+ | 1 topping | $14.75 | $20.95 | +-----------------+---------+---------+ | 2 toppings | $15.95 | $22.95 | +-----------------+---------+---------+ | 3 toppings | $16.95 | $24.95 | +-----------------+---------+---------+ | Special | $18.50 | $26.95 | +-----------------+---------+---------+
Hints
- The
csvmodule has areaderthat turns each line of a file into a list of strings, so a whole file becomes a list of lists. openraisesFileNotFoundErrorwhen the file isn’t there. Atryandexceptis how you catch it.tabulatehas just one function, also calledtabulate. Give it the body rows, thenheaders=for the first row, thentablefmt="grid". Its documentation lists every format it knows.- Check the arguments before you try to open anything. A file called
sicilian.txtshould get “Not a CSV file” even if it exists. str.endswith()is a handy way to look at the end of a filename.
Need a hint?
The first row of the file is the header, and everything after it is the
body of the table. If you have all the rows in a list called rows,
then rows[0] is the header and rows[1:] is the body.
To Get Full Credit
It’s more important that you submit a working solution than that you do everything below. Submit early, then keep improving and resubmit as many times as you like.
- Fill in
check_argsandformat_tablewith real code, and add one more>>>example of your own to each docstring covering a case not already shown (a file name with.csvin the middle, likemy.csv.txt, forcheck_args, for instance). Don’t try to doctestmainorread_rows; they depend on files and on the command line. - At the end of the program, add a comment describing any challenges you ran into or what you’d improve if you did this again. A sentence or two is fine.
Style and Submission
Run these one at a time, from inside your pizza folder. If you’re
coming back in a new terminal, reactivate your virtual environment
first (source myvenv/bin/activate) or check50 will report a
missing tabulate module even though your code is correct.
Check your style:
style50 pizza.py
Check your correctness:
check50 porttack/cs50/problems/py/pizza
Submit your work:
submit50 porttack/cs50/problems/py/pizza
Glossary
- CSV (comma-separated values): A plain text file where each line is a row and commas separate the columns.
- command-line argument: A word typed after the program’s name,
like
regular.csvinpython pizza.py regular.csv. Python collects them in the listsys.argv, with the program’s own name first. - exception: What Python raises when something goes wrong, such as
FileNotFoundErrorwhen a file isn’t there. Atryandexceptcatches it instead of crashing. - package: Code someone else wrote that you can import, such as
tabulate.
Version history
- Loading commit history…