Lab Assignment 3

This assignment is due by 10:00 p.m. on Friday, October 20. Material from this lab may appear on the midterm (Tuesday, October 24).

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

(1) If you're just getting enrolled in the class, do everything on the course refrence sheet (the syllabus) under the heading "What to do this week to get started in ICS 31." This includes registering yourself at Gradescope (in response to their Email message) and at piazza.com, filling out the questionnaire at eee.uci.edu/survey/ics31.s17.q, and arranging with your TA to turn in the lab assignments you missed.

(2) This is a critical time in the course: If you have been a passive partner in the lab, letting your (possibly more experienced) partner take the lead, smiling and nodding and not taking your turns as "driver" at the keyboard or not taking the trouble to slow your partner down for an explanation of what's going on—if you haven't been fully engaged in the course and feel as though the material is passing you by—there's still time (barely) for you to turn it around and really learn the material. Read the book; do the practice problems; ask questions about points that aren't clear; consider LARC tutoring; check out the supplementary resoures on the resource page. Everyone is capable of doing well in ICS 31, but not without learning what you need for the labs and the quizzes and exams.

(3) Re-read setions 3.3 and 3.5 and read sections 3.1 and 3.2 of the Perkovic text, trying the practice problems (before looking at the solutions). We have already worked with assignment statements and distinguished some data structures as immutable while others are mutable. Section 3.4 explores these issues further, but we recommend skipping that section until after the midterm.

In section 3.2, look at the flowchart diagrams. They're a good way to visualize the semantics (meaning, behavior) of the control structures.

(4) Become comfortable with the terminology of functions: function definitions, function calling, arguments (the values you call the functions with), parameters (the names of those values inside the function, defined in the function header/signature/def line), what it means when we say a function "takes a number and a string", what we mean by "this function takes two inputs" [it does not involve the input() function], what it means for a function to return a value, the difference between defining a function and calling it.

(5) (Re-)read about the design recipe. You will follow these steps for every function you write in this course. (One potential exception is functions that have side effects—that don't return a value but change something external, like keyboard input or printing or drawing on the tkinter canvas. Those functions can't easily be tested with assert statements.)

(6) This assignment may use the following Python features that we covered in class or will cover early this week: namedtuples (how to define and construct them, how to retrieve individual fields, how to change them); if statements and boolean expressions; simple for-loops to process each item in a sequence (for i in L:); assert statements

(7) Read through the Lab Work part of this assignment before you come to the first lab section meeting. This will give you an idea of what you'll be doing with your partner, but it also contains a variety of explanations of important course material, things you'll need for the assignment, quizzes, and exams. We won't bother repeating this instruction in next week's lab (or thereafter), but of course you should continue to follow it.


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

Programmers hate reinventing the wheel. It is almost always better to use an already-written tool than to rewrite it from scratch. (There could be exceptions: The existing tool could be of unknown quality or could be protected by copyright law.) This applies even to assignments and exams in ICS 31. If some code you wrote earlier in the assignment or exam would help you to solve a later problem, you are expected to use that code (unless there are explicit instructions not to) and you'll receive less credit if you re-implement the same thing a second time. So keep your eyes out; code and techniques in this lab and others will help you in your later work.

(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) For this assignment, you will create a Python file called lab3.py. Type your answers to all the parts of this assignment into this file; non-Python text can be in comments or multi-line (triple-quoted) strings.

On the first line of the file type a comment with the names and IDs of both partners and some other identifying information, like this:

#  Paula Programmer 11223344 and Andrew Anteater 44332211.  ICS 31 Lab sec 7.  Lab Assignment 3.

The Python code in your file should produce the specified results when you run the file. When you've finished the assignment, you'll submit this file via Checkmate. (It's a good idea if each partner keeps a copy of the lab work at the end of each session, just in case someone can't make it to the next lab.)

It is also helpful in the grading if you separate your code for each part of the assignment in your lab3.py file. Indicate each part of the assignment in the file with comments like this:

# Part (c)
and also include print statements so the different parts are distinguished in the output, for example:
print()  # Leaves a blank line.  print('\n') leaves two blank lines.
print('---------- Part (c) ----------')

We won't bother repeating these instructions in later lab assignments, but unless we specify otherwise you should still prepare your lab work this way.

(c) Python exercises: Follow the design recipe for the functions specified here and for every function you write. Include enough assert statements with enough different values to demonstrate that the function works correctly.

(1) Implement the function abbreviate that takes the name of a month as input and returns its three-letter abbreviation. In fact, it should take any string as input and return its first three characters, as these examples illustrate:

assert abbreviate('January') == 'Jan'
assert abbreviate('abril') == 'abr'

(2) Implement the function find_area_square that takes as input a number representing the length of one side of a square and returns the area of that square.

assert find_area_square(1) == 1
assert find_area_square(5) == 25

(3) Implement the function find_area_circle that takes as its input the radius of a circle and returns the area of that circle. (If you don't remember the formula, Google it.)

assert find_area_circle(1) == 3.14159
assert find_area_circle(5) == 78.53975

(Your values may vary slightly depending on what value you use for pi.)

(4) Implement the function print_even_numbers that takes a list of integers as input and prints each even number on the list. Calling print_even_numbers([2, 47, 31, 99, 20, 19, 23, 105, 710, 1004]) would produce this output in the shell window:


(5) Implement a function called calculate_shipping that takes one argument, the weight of a package to be shipped. It returns the price for shipping the package, calculated as follows: For anything under 2 pounds, the rate is $2.00. For packages of 2 pounds but under 10 pounds, the rate is $5.00. For packages of 10 pounds or more, the rate is $5.00 plus $1.50 per pound for each pound over 10.

assert calculate_shipping(1.5) == 2.00
assert calculate_shipping(7) == 5.00
assert calculate_shipping(15) == 12.50

The tests (assert statements) in your code should be thorough. In particular, they should test each category of shipment and test weights that are right on the border between categories.

(6) A square is a special kind of rectangle, one with all four sides the same length. Implement a function called create_square that takes three arguments—the x-coordinate and the y-coordinate of the upper-left corner and the length of a side. Your function should draw in the tkinter window a square as described by the arguments. It should do this by calling the predefined tkinter function create_rectangle. Your job is to take the parameters passed to create_square and use them in forming the arguments to create_rectangle. (Note that you can't test tkinter code using assert statements. You'll just have to make enough calls so you can see that your function appears to be working.) For now, just omit the fill= argument.

(7) A circle is a special kind of oval; in tkinter terms, a circle is an oval that's inscribed into a square rather than some other kind of rectangle. Similarly to the previous problem, implement a function called create_circle that takes three arguments—the x-coordinate and the y-coordinate of the upper-left corner of the square that encloses the circle and the diameter of the circle—and draws in the tkinter window a circle as described by the arguments.

(d) When we sort a list of items, we need a basis on which to compare the items to see whether one is bigger than another. If it's a list of numbers, Python just compares the numeric values; if it's a list of strings, Python compares the strings alphabetically. But what if we're sorting a list of restaurants? As we saw earlier, the sort() method uses the first field of a Restaurant object (in our case, its name) for comparison. We call the basis of comparison the sort key. Wouldn't it be convenient if we could sort lists of complex objects based on some other sort key? [Note: nothing here in part (d) requires a for-loop or an if-statement.]

(d.1) Define a function called restaurant_price that takes one argument, a Restaurant, and returns the value of the price field of that Restaurant. This is quite short and easy. But note that whenever a lab problem asks you to define a function, you also need to include in your file some calls to that function that test it, calls that demonstrate that the function works correctly. So define a list containing a few Restaurants (maybe copy the list from last week's lab) and print out the results of calling restaurant_price on those Restaurants. (You may use assert statements, which are a way of partially automating this testing process.)

(d.2) Write a sequence of statements that prints out the list of Restaurants RC in order from least expensive to most expensive (best dish). [Hint: As an argument to the sort() method, say key=restaurant_price. Take a second to read this again, paying attention to the terminology so you know what this is telling you to write in Python. Interpreting technical terminology is an important skill; it may also help if you look at the entry for sort() in help(list). Giving key=restaurant_price as an argument to sort() tells sort() how to get the value from each Restaurant that it will compare: By default (without our saying anything), it used the first field, the name of the Restaurant; the key= argument lets us specify a function that takes a Restaurant as input and returns a value—in our example, its price field—to use instead of the name when comparing Restaurants during the sorting process. What's tricky is that we supply the name of a function after key=, but as we'll see later, functions can be useful as components of other computations.]

(d.3) Write a function called costliest that takes a list of Restaurants as its argument and returns the name of the highest-priced restaurant on that list (based on the price of the restaurant's best dish, of course). [You need to pay close attention to the wording of technical specifications like this. This function returns a string, the name of the costliest restaurant; it doesn not return the whole Restaurant object.]

To test this function, create a short list of Restaurants and print the value of calling costliest with that list as its argument.

(d.4) You may have noticed that after calling your costliest function with a list of Restaurants, two things happen: costliest returns the name of the restaurant and the list has been reordered by price. Try it out: Print the list before calling costliest and then print it again afterwards; whatever its order beforehand, it's ordered by price after the call. This is a side effect: something a function does besides compute and return a value. Sometimes side effects are a required part of solving a problem: printing, for example, or drawing on a tkinter canvas. But when we're manipulating data in our program, generally it's best if a function does its work by returning a value and leaves everything else unchanged.

We can accomplish this in the function costliest by using the predefined function sorted() rather than the method sort(). With a list L, sorted(L) returns a list containing the items in L in order (which can be influenced with the same key= and reverse= arguments as with sort()), but without changing the order of the original list L.

Rewrite your costliest function (call it costliest2) to produce the same result as before but without any side effects. [You may have to make some small adjustments since the method call can stand alone as a statement but the function call must be included in some statement that uses its value.]

(e) We can combine assignment statements, for-loops, and if statements to perform a wide range of tasks with lists. Suppose we have a bookstore with each book defined as follows: Book = namedtuple('Book', 'author title genre year price instock'), where "genre" is the category of book (e.g., cookbook or mystery or sports), "year" is the year of publication, and "instock" is the number of copies of that book we have available to sell. Make up a half-dozen Book structures and combine them into a list called BSI (for "book store inventory").

(e.1) Write a sequence of statements that prints the title of each book in BSI, one per line.

(e.2) Write a sequence of statements that prints the title of each book in BSI, one per line, in alphabetical order; do this without changing the original order of BSI.

(e.3) Write a sequence of statements that raises the price of each book in BSI by 10%; this does change the value of BSI.

(e.4) Write a sequence of statements that prints the title of each book in BSI whose genre is Technology.

(e.5) If we ask how many books there are in our list BSI, there are two possible answers: One is just the number of items in the list, each representing a different author/title combination (book publishers call this the number of "titles"); the other is the number of individual, physical books in the store's inventory (i.e., the sum of all the instock figures of all the "titles" in the list).

Write a sequence of statements that creates a new list containing the Books ("titles") in BSI that were published before 2000 and a second new list of Books published in 2000 or later. Then write a sequence of statements that prints one of these phrases: More titles before 2000 or More titles 2000 or later. Finally, add to whichever message you print the number of titles in each category, in a form like this: More titles 2000 or later (345 vs. 189). [Hint: To create a new list in this way, start with an empty list. Then, each time you find a new Book that belongs on the list, add that Book to the list. When you've gone through the original list, your new list will have the Books you want. Second hint: There's a simple predefined function that will tell you the number of Books on a (newly created) list. Third hint: Remember that we're writing general code, code that will work for a list of 10 books or of 10,000. When instructions say, to print something "in the form (345 vs. 189)", don't think, "That can't make sense; we've only created six books." Think, "Oh, right, this code might have to deal with more than 500 books."]

(e.6) The value of the inventory of a particular book is the price of that book times the number of copies we have in stock. Write a function called inventory_value that takes a Book as its argument and returns the value of our inventory of that book. Then write a function called top_value that takes a list of Books as its argument and returns the Book object (from the list) that has the highest-value inventory. Finally, write a sequence of statements that prints a line in this form: The highest-inventory-value book is War and Peace by Tolstoy, Leo at a value of $ 595.00.

(f) Write a series of tkinter statements to draw a face, with a mouth, a nose, and two eyes. Use your eye-drawing code from last week's lab. But don't just copy those lines twice! Even if you use copy and paste to avoid tedious retyping, duplicate code is always a problem: It makes the program longer, and if you have to change it, you have to change every copy. If you skip changing one copy of the duplicate code, your program becomes an inconsistent mess. Happily, we've learned how to avoid writing duplicate code in this situation: We design a function called draw_eye to contain our eye-drawing code; then we call that function twice, once for each eye. But there's one more step, because we don't want to draw both eyes in the same place. Our draw_eye function needs parameters that specifies the starting point for the drawing; then we can call it twice, one with a starting point that's offset from the other.

Write a function called draw_face that calls functions (that you also write) called draw_eye, draw_nose, and draw_mouth. Your nose and mouth don't have to be as fancy as your eyes; in fact, make them as crude and simple as you can to start with, and only refine them if you have time. Include at least two calls to draw_face in your lab3.py file.

(If you're feeling ambitious and have the time, parameterize other aspects of your feature-drawing functions so you can easily draw faces with different-color eyes, different-sized noses, and so on.)

(g) Remember that each partner must complete a partner evaluation form and submit it individually in 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

your lab3.py file containing your solutions to parts (c) through (f). It would be an excellent idea to go back and re-read those parts carefully now, to make sure you've completed all the steps specified. It would also be an excellent idea to run your file one last time to make sure all the correct results appear, with no error messages. (If you run into problems, it's probably because you have more than one function with the same name. Change one of the names [everywhere necessary] and try to Run again.)

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 form closes on Saturday morning.


Written by David G. Kay in Fall 2012 for ICS 31. Modified by David G. Kay, Winter 2013 and Fall 2013. Python exercises by David Lepe, edited by David G. Kay, Winter 2014, Winter 2015, Fall 2015, Fall 2016, and Spring 2017.

David G. Kay, kay@uci.edu
Thursday, October 19, 2017 2:41 PM