← CS50 Problems

Problem 12

Pizza

Read a pizza menu from a CSV file and print it as a table, checking the command line first.

A few lines of comma-separated text on the left, with an arrow pointing right to the same information drawn as a table with a header row Regular,Small,Large Cheese,$13.50,$18.95 1 topping,$14.75,$20.95 Special,$18.50,$26.95 Regular Small Large Cheese $13.50 $18.95 1 topping $14.75 $20.95 Special $18.50 $26.95
A CSV file is just text with commas. Your program turns it into a table people can read.

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 tabulate with its grid table format. The first row of the file is the header.
  • Exit via sys.exit with the message Too few command-line arguments if the user does not specify exactly one command-line argument, or Too many command-line arguments if they specify more than one.
  • Exit via sys.exit with Not a CSV file if the specified file’s name does not end in .csv.
  • Exit via sys.exit with File does not exist if 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 csv module has a reader that turns each line of a file into a list of strings, so a whole file becomes a list of lists.
  • open raises FileNotFoundError when the file isn’t there. A try and except is how you catch it.
  • tabulate has just one function, also called tabulate. Give it the body rows, then headers= for the first row, then tablefmt="grid". Its documentation lists every format it knows.
  • Check the arguments before you try to open anything. A file called sicilian.txt should 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_args and format_table with real code, and add one more >>> example of your own to each docstring covering a case not already shown (a file name with .csv in the middle, like my.csv.txt, for check_args, for instance). Don’t try to doctest main or read_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.csv in python pizza.py regular.csv. Python collects them in the list sys.argv, with the program’s own name first.
  • exception: What Python raises when something goes wrong, such as FileNotFoundError when a file isn’t there. A try and except catches it instead of crashing.
  • package: Code someone else wrote that you can import, such as tabulate.