Lab Assignment 5

This assignment is due by 10:00 p.m. on Friday, November 3.

Preparation (Do this part individually, before coming to lab)

(1) If you're planning to use your own computer for some of your lab work, download the complete first version of the restaurants program, save it on your computer, and run it to make sure it works in your environment. You don't want to waste lab time on this.

(2) Read section 4.1 in the textbook; some of this is material we've already seen.

(3) String manipulation isn't conceptually hard, but it requires meticulous attention to detail, so it's particularly important that you try the practice problems. At www.pythontutor.com you can run simple Python code and see the values change in memory; this is a helpful tool, but use it to check the work you do by hand. It's tempting just to copy in the code, watch it work, and then copy down the answer, but that totally defeats the purpose; learning happens when you do the problems, not when you see the answer.

If you run into trouble, check with your TA right away.

(4) Read the rest of the assignment so you can come to the lab on Monday prepared to start work.


Lab Work (Do this part with your partner in lab)

(a) Choose a partner for this assignment and register your partnership using the partner app, ideally by Monday. Remember that you'll choose a different partner for each lab assignment, so you'll work with this partner only this week. Make sure you know your partner's name (first and last) and contact information (Email or cellphone or whatever) in case one of you can't make it to lab.

(b) Prepare your lab5.py file as in previous labs, including a line like this:

#  Paula Programmer 11223344 and Andrew Anteater 44332211.  ICS 31 Lab sec 7.  Lab asst 5.

(c) Let's define a Dish (that might be served at a restaurant) with three fields: a string for the name of the dish, a number for its price, and a number for the number of calories in the dish.

As you write the functions in this problem (and every problem), it's essential that you follow the design recipe, especially specifying the types of the parameters and return values. You'll need to distinguish between functions that take single items and functions that take lists of items, for example; you'll run into trouble if you're not clear on that to start with. Writing examples (in the form of assert statments) is similarly essential.

(c.1) Define a namedtuple for representing dishes like this and create three actual Dish objects (just make up the values for each Dish and assign them to variables named d1, d2, and d3).

(c.2) Write a function called Dish_str that takes a Dish and returns a string in this form:

Paht Woon Sen ($9.50): 330 cal

(You don't have to format the dollar amount perfectly at this point.)

(c.3) Write a function called Dish_same that takes two dishes as arguments and returns True if the names of the two dishes and their calorie counts are equal (and False otherwise).

Write some tests using assert statements (perhaps including d1, d2, and d3); they should include calls with two identical dishes, two dishes that are the same except for their price, and two dishes that differ in their names, calorie counts, or both. Of course your tests should be included in your lab5.py file, and of course you should have tests like this for every function you write, except maybe the ones that print instead of returning a value. Coming up with thorough tests is another programming skill. Some people enjoy trying to "break" software; they become software quality assurance (testing) experts.

(c.4) Write a function called Dish_change_price that takes a Dish and a number and returns a Dish that's the same as the parameter except that its price is changed as follows: The number (positive or negative) represents a percentage change in price (so that 100 would double the price and –50 would cut it in half). (This may require you to think a little about the arithmetic you need to compute this result. Figure it out before you write any code; come up with a half-dozen different examples and their results.)

(c.5) Write a function called Dish_is_cheap that takes a Dish and a number and returns True if the Dish's price is less than that number (and False otherwise).

(c.6) Now create a list called DL of at least five Dish objects. Play around with this list in the shell for a minute or two (take its length, sort it, append another Dish to the end) to make sure it works as you expect. Next create another list called DL2 that contains at least four dish objects. Then create one big list by extending DL with DL2. (Note the difference between the append() method and the extend() method; take a minute to be sure.)

Write a function called Dishlist_display that takes a list of Dishes and returns one large string consisting of the string representation of each dish followed by a newline ('\n') character. We've done something similar to this with a collection of restaurants. Write some tests, as usual. Then use a print statement to print the string representation of all the dishes in the big list you created above.

(c.7) Write a function called Dishlist_all_cheap that takes a list of Dishes (note how this is different from Dish_is_cheap) and a number and returns True if the price of every dish on the list is less than that number. Of course Dishlist_all_cheap will call Dish_is_cheap; solutions that duplicate the code instead of using an already-defined function would receive little credit.

(c.8) Write a function called Dishlist_change_price that takes a list of Dishes and a number representing a percentage change and returns a list of Dishes with each price changed by the specified amount. (Since lists are mutable, ask yourself how you'd write this differently if the specification were to change the list itself rather than returning a new list with changed prices.)

(c.9) Write a function called Dishlist_prices that takes a list of Dishes and returns a list of numbers containing just the prices of the dishes on that list.

(c.10) Write a function called Dishlist_average that takes a list of Dishes and returns the average price of those dishes. (Again, call previously defined functions wherever possible; don't reinvent the wheel.)

(c.11) Write a function called Dishlist_keep_cheap that takes a list of Dishes and a number and returns a list of those dishes on the original list that have prices less than that number. This is much like keeping the consonants in a string.

(c.12) Create a list of at least 25 Dishes. Just make them up, but for this part it's perfectly fine to trade dishes with classmates other than your partner. Just check that the dishes you receive are correctly formed.

Write a function called before_and_after that takes no parameters. It prompts the user for interactive input of a number representing a percentage change in prices; then it prints the result of Dishlist_display on your big list of Dishes; then it changes all the prices of the Dishes on the big list; then it prints the result of Dishlist_display again (reflecting the changed list of Dishes).

(d) Copy the complete first version of the restaurants program to your lab machine (see the link above). Call it restaurantsd.py (because this is for part (d) of the assignment); you'll turn this file in separately, along with your lab5.py file. Run the code to make sure you've installed it correctly.

Now, try the tasks described below with this advice in mind: When you modify large, unfamiliar programs, you want to be especially careful (i) to make and test only small changes, one by one, testing each change as you go, (ii) to make your changes deliberately, with high confidence that they will work as intended, rather than just changing things haphazardly and hoping they'll work, and (iii) make each small set of changes on a new copy of the code, so if you have to "roll back" those changes you won't have far back to go because you'll have a copy of your most recent, stable version. It's easy to lose your way when navigating around a large program; these steps will help keep you on track. [Don't think that you're too good a programmer for this advice to apply to you. It's a terrible feeling when you realize you no longer understand how your program works—and we're at the stage where your programs are big enough that nobody else, not the TA or the lab tutor—will always be able to zero in on the problem and fix things. Debugging is hard, and the best way to debug is to design and implement carefully so you introduce as few bugs as possible in the first place.]

(d.1) Change the command for adding a new restaurant to n instead of a.

(d.2) Add an additional menu choice:

e: Remove (erase) all the restaurants from the collection

If the user types e and then p, for example, nothing would be printed. [This will require modifications or additions in a few places. You can do the main part of the work very easily indeed, with a single function call.] Test out your modified program interactively to satisfy yourself that it works as intended.

(d.3) Add one new feature to the program:

c: Change prices for the dishes served

When the user types c, the program should ask the user for an amount representing a percentage change in price, as described above. Then it should apply that price change to the prices for all the restaurants in the collection. So far with this program, each Restaurant has just one dish and price; that's what you're changing here. Later in this lab, not now, we'll add lists of Dish structures. [You should design this in the same way you designed it for a list of Dishes. Write a separate Restaurant_change_price function, for example, and a Collection_change_prices function that you would call from handle_commands.]

(e) Above we defined a Dish and we worked with lists of Dish objects. Let's call a list of Dishes a Menu and let's redefine a Restaurant to have a menu instead of just one best dish and price; we'll also define a couple of examples:

Restaurant = namedtuple('Restaurant', 'name cuisine phone menu')
r1 = Restaurant('Thai Dishes', 'Thai', '334-4433', [Dish('Mee Krob', 12.50, 500),
                                                    Dish('Larb Gai', 11.00, 450)])
r2 = Restaurant('Taillevent', 'French', '01-44-95-15-01', 
				[Dish('Homard Bleu', 45.00, 750),
				 Dish('Tournedos Rossini', 65.00, 950),
				 Dish("Selle d'Agneau", 60.00, 850)])				

Do this part in your lab5.py file.

(e.1) Write a Python expression that defines r3 as a Restaurant object for the French restaurant Pascal whose phone number is 940-752-0107; they serve escargots for $12.95 (250 cal.), poached salmon for $18.50 (550 cal.), rack of lamb for $24.00 (850 cal.), and marjolaine cake for $8.50 (950 cal.).

(e.2) Write the function Restaurant_first_dish_name that takes a Restaurant as its argument and returns the name of the first dish on the restaurant's menu. Remember to write the examples and expected results (as assert statements) before you write the function; do this for every function, whether we remind you or not. You should include code to check whether the menu has zero dishes and return the empty string if so.

(e.3) Write a function called Restaurant_is_cheap that takes two arguments, a Restaurant and a number, and returns True if the average price of the Restaurant's menu is less than or equal to the number. [Hint: Some of the functions you wrote above can be used here.] As you work on this, you'll find it essential to keep track of what part of the data you're working with: Is it a Restaurant object, a string, a number, a list, a Dish, ...?

(e.4) In fact, counting the whole collection of Restaurants, there are four "layers" to the data in this example:

It helps keep everything straight if you write a separate function for each layer. If we want, for example, to write the function Collection_raise_prices that takes a Collection and returns the Collection with the price of every dish at every restaurant raised by $2.50, it will be easiest if Collection_raise_prices calls, for each Restaurant, a function Restaurant_raise_prices (that takes a restaurant and returns that restaurant with all its prices raised by $2.50). Restaurant_raise_prices in turn would call Menu_raise_prices, which takes a Menu, applies a function like Dish_raise_price to each Dish on the Menu, and returns the modified menu.

Create a Collection from the three Restaurants defined above (plus more, if you like).

Write the function Collection_raise_prices as described above. Simply raise each price by $2.50.

Then, write the function Collection_change_price that works as described above but takes a second parameter, a percentage by which to change each price (as we did above). Note that you'll have to keep passing the percentage along to each successive function, since it's not until you get to the bottom (the price of an individual Dish) that you'll actually use that number.

(e.5) Write the function Collection_select_cheap that takes a Collection and a number and returns a list of all the Restaurants in the collection whose average price is less than or equal to that number. Use the functions described above where appropriate.

(f) Now, let's try to incorporate menus into the full Restaurants program. Start by downloading a clean copy of the first version of the restaurants program; if you're confident in the version you modified in part (d), you may start with that version instead. For this part, create a separate file called restaurantsf.py. You'll turn that file in along with your lab5.py file and the restaurantsd.py file you created earlier.

Our advice about taking things slowly and methodically applies here as never before. If you find yourself running out of time at the end of the week, it may well be because you weren't methodical enough about making changes to this code.

The original program has a section for Restaurants and a section for Collections. You'll want to add a section for Dishes and a section for Menus. In the Dish section, include the functions Dish_str (which you already wrote) and Dish_get_info, which works along the same lines as Restaurant_get_info.

Now, in the Menus section, write a Menu_enter function that repeatedly asks whether the user wants to add a Dish. If the user enters yes, the function prompts the user to create a Dish and adds it on to the growing list of dishes; when the user enters no, the function returns the compiled list of Dishes. Now, where do we have to call Menu_enter and take the Menu it returns, including it into the whole data structure in the appropriate place? In the Menus section, you'll also need someting to create a display string for the menu of dishes; you'll also need to find where in the program to call the function that generates the Menu display string.

Next, incorporate your price-changing code into the program so that ultimately, the main menu gives the same option as above:

c: Change prices for the dishes served

When the user types c, the program should ask the user for an amount representing a percentage change in price, as before, and it should apply that price change to the prices for all the Dishes in all the Restaurants in the collection.

Finally, if you have time, also incorporate a top-level command that selects restaurants with prices at or below a specified value, reusing the code you defined above where appropriate.

(g) Write a function called letter_count that has two strings as parameters. The function examines its first parameter, counting certain letters; the second parameter is a string that specifies which letters to count. Thus, letter_count(some_message, 'aeiou') would return counts of the vowels in the string some_message and letter_count(some_message, ' \t\n') would return counts of the white-space characters (space, tab, and newline). The function should return its counts as a list of Count namedtuples, where a Count namedtuple has two fields, letter and number. [Okay; go back and read that again, two or three times, until it's clear. It's important to be able to read specifications that use technical terminology. The following example may also help.] Calling letter_count('The cabbage has baggage', 'abcd') should return [Count(letter='a',number=5), Count(letter='b',number=3), Count(letter='c',number=1), Count(letter='d',number=0)].

So how do you go about this? You need to break it down into parts. First, follow the design recipe: Write a function header with the types of the parameters and the type of the return value; write a docstring "purpose statement"; and write some examples of calls to this function and the expected results (in the form of assert statements). That may take a few minutes, but it's time well spent at this stage. Second, define the Count namedtuple. Third, and this is always a good idea when you're designing a function that does the same thing (count occurrences) with multiple values (the vowels or whatever), design and write a function that does the task with one value (i.e., takes a string and a single character and returns a single Count namedtuple with the character and the number of times it occurs in the string); the body of this function can be one line that calls a string method (see if you can find it in the book or in help(str)). Fourth, write the body of the main letter_count function, where the task is to call the count-one-character function for each of the characters you're counting, building up the list of Counts and returning it at the end.

For purposes of this assignment, treat upper and lower case characters as distinct; that will make the coding easier. But you should consider how you could modify your function to count upper and lower case versions of the same letter together.

(h) Remember that each partner must complete a partner evaluation form and submit it individually. Do this using the partner app. Make sure you know your partner's name, first and last, so you can evaluate the right person. Please complete your evaluation by the end of the day on Friday, or Saturday morning at the latest. It only takes a couple of minutes and not doing it hurts your participation score.

What to turn in: Submit via Checkmate three files: your lab5.py file containing your solutions to parts (c), (e), and (g), and the two files restaurantsd.py and restaurantsf.py from parts (d) and (f), respectively. Remember what we've said in previous labs about rereading the assignment and rerunning your Python files.

Also remember that each student must complete a partner evaluation form; these evaluations contribute to your class participation score. Get in the habit of doing this every week on Friday after you've submitted your assignment; the evaluation closes on Saturday morning.


Written by David G. Kay in Fall 2012 for ICS 31, based in part on assignments from ICS H21 and Informatics 41. Modified by David G. Kay, Winter 2013, Fall 2013, Winter 2014, Fall 2014, Winter 2015, Fall 2015, Spring 2017.

David G. Kay, kay@uci.edu
Saturday, October 7, 2017 5:27 PM