**Homework 3: Vend and Function** **Reed CSCI 121 Fall 2022** *complete the exercises by 9am on 9/20* We begin this homework with some borderline diplomacy. You will invent a friendly vending machine to help US citizens feel comfortable venturing into the great white northern country of Canada. Writing that script's call will call on your Python skills to perform input, output, conditional execution, and integer division. It will be the great script named `canada.py`. The remaining exercises each ask you to invent a Python *function*. The Python file that you submit for each should contain that function's definition using the `def` statement. We'll talk more about this just below. In short, they will not be interactive scripts. Instead, you will test your work by loading that definition's file into the Python interpreter. You'll try out that function on a lot of different parameters, making sure that it returns the correct value in each case. (##) On Python functions This week we ask you, for exercises 2 through 7, to devise a Python function rather than a Python script. You'll use the `def` statement to describe a function that computes and returns a value based on the values of the parameters that are passed to it. For example, the fifth exercise has you write a function `stars`. You'll write code into a Python text file that includes a line ~~~ python def stars(width, height): ~~~ Here, `width` and `height` are the *formal parameters* that stand in for the *actual parameter* values sent to the function when it is used by other code. The lines after this `def` line describe the behavior of the function, *each line indented by 4 spaces*, ultimately *returning* a value that the function is asked to compute. (In the case of `stars`, the function will return a string of characters.) Below, for example, is a function that performs temperature unit conversion, getting a temperature in degrees fahrenheit, then reporting the equivalent in degrees celsius: ~~~ python def convert(degrees_f): degrees_c = (degrees_f - 32.0) / 9.0 * 5.0 return degrees_c ~~~ Using this function's code is a bit different than running a script's code. We need to call the function in order to use its code. One way to test the code is to load the function definition into an *interactive* Python session, and then call the function with some parameters. If, for example, we imagine that the `convert` function is defined in a source file `convert.py`, we could do the following: ~~~ none jimfix@C02F48U1ML7H samples % python3 -i convert.py >>> convert(212.0) 100.0 >>> convert(32.0) 0.0 >>> convert(-40.0) -40.0 ~~~ Here, because we ran the Python command with `-i`, the code was loaded *interactively*, and we are able to test the function within the Python interpreter. When an exercise asks you to write a single function, it is more than okay to define *other* functions in the same `.py` file, ones that you use to help you compute the value expected for that one specified function. Indeed, writing these *helper functions* can sometimes be a great way to structure your code, especially since it allows you to test portions of your work---to get those subcomponents working---as you construct the function they serve. For many of these functions you'll want to use `if` statements to do their calculations. Also, you want to `return` the computed results rather than use `print` statements. Functions return values. (##) Submission and Autograding Submit each Python source file containing your script code to the Gradescope site. The problems below are labeled the way they will appear as submission items in Gradescope. As before, we'll run a series of tests on a problem submission to see if your code is correct. The autograder will your scripts, trying out different input strings. It checks to see whether the lines output by `print` match what we expect. Before submitting, you should run your scripts several times, and with different inputs, to make sure that your code is working. Only submit it when you have reasonable confidence that it is correct. It's hard to anticipate every possible input, but you should work to anticipate as much as you can. Some of the problems allow you to assume that a user won't enter certain kinds of inputs. When we allow you to make assumptions like that, you do not need to check inputs that aren't assumed to happen. (##) Scoring Each problem is worth 30 points. 10 of the points are awarded based on the number of tests you pass. An additional prize of up to 10 points will be awarded if *all* the tests pass by the due date. You'll earn fewer of these prize points if you have to submit a problem several times to get all the tests to pass. Another 10 points will be given after the deadline as a result of our own assessment of the quality of your code. **Summary:** * **test score:** *(automated)* partial or full credit for passing each test * **prize score:** *(automated)* additional credit for passing all the tests before the deadline * **style score:** *(by hand)* partial or full credit for code of reasonable quality (#) Problems (##) `[HW3 P1]` canada **Note: you will get the full prize points for this problem if it is submitted on-time, regardless of the number of points.** You've been hired by the Canadian government to design a vending machine. It will be placed just north of the US-Canadian border, providing some information about how temperature readings differ in Canada, and offering visitors the opportunity to purchase bags from a variety of potato chips. The vending machine is driven by a Python script that you will write. Get it all correct, and Canada will thank you for your fine effort. Here is a sample interaction with the script you write: ~~~ none |||| |||| |||| _/TT\_ |||| |||| \\||// |||| |||| '-||-' |||| |||| |||| Welcome to Canada! ------------------ Enter the current temperature in whole degrees Celsius: 30 That means that it is 86 degrees Fahrenheit outside. Let's get the cost of potato chips here in Canadian dollars and cents... Enter the number of dollars per bag of chips: 2 Enter the number of cents per bag of chips: 20 Okay. Enter the number of bags would you like to purchase: 4 Enter the chip flavor you prefer [plain, pickle, or ketchup]: pickle Your total is $8.80 Canadian. Please give me 4 toonies and 1 loonie. [hit RETURN]: Thank you! Here are your pickle chips. ~~~ As you can see, the first seven lines greet the visitor with a welcome message. That greeting is followed by a blank line and a prompt for the current temperature. You should expect them to enter the temperature as a whole number in degrees celsius, and then your script should report the temperature as a whole number in degrees Fahrenheit so that US visitors will be well-informed about the weather. After that, a blank line is output and then the vending machine asks for three integers-- the cost in Canadian dollars and cents of a bag of potato chips and also the number of bags of chips they would like to purchase. (**Note:** your code must replicate the "`bags would you like`" typo in the interaction.) In a typical interaction like what's shown above, a user will want *some* bags of chips and the chips cost *some* money. If so, it immediately asks them to enter (a string) for the flavor of chips they prefer. In a typical interaction, also, they will enter one of `plain`, `pickle`, or `ketchup`. The script should finish by printing a blank line outputting the total purchase cost as `$D.CC` where `D` is the total dollars, one or more digits long, starting with the first non-zero digit if more than one digit. And `CC` is the number of cents as two digits. It should then tell the user what number of two- and one-dollar coins to insert. (The machine only takes these two kinds of whole-dollar coins). * If they only need to insert one dollar the request should be for "`1 loonie`". * If instead they need to insert two dollars the request should be for "`1 toonie`". * If three dollars the request should be for "`1 toonie and 1 loonie`". * If four dollars say "`2 toonies`". * If five then "`2 toonies and 1 loonie`". And so on. Note that it should request at most one one-dollar loonie coin. In the example interaction above, because the total cost of $8.80 requires payment of nine whole Canadian dollars, the machine requests "`4 toonies and 1 loonie`" and then waits for the user to hit their `[RETURN]` key. Having done that, it thanks them for their purchase and gives a message describing what flavor of chips they are vended. Below, we also describe less typical interactions, and how they should be handled by your code. These are the cases when: * They choose to purchase 0 bags. * They purchase some bags of chips, but the chips cost no money. * They purchase some bags of chips, but they request a flavor that isn't one of the three offered. --- **A Transaction With No Chips Requested** If they choose to purchase no chips by entering `0` for the number of bags, then the machine should not ask for the chip flavor or ask for money. Instead, it should end the transaction with a blank line and the message ~~~ Okay! Thanks for chatting about our beautiful weather. ~~~ Here is a full interaction with this scenario: ~~~ none |||| |||| |||| _/TT\_ |||| |||| \\||// |||| |||| '-||-' |||| |||| |||| Welcome to Canada! ------------------ Enter the current temperature in whole degrees Celsius: 30 That means that it is 86 degrees Fahrenheit outside. Let's get the cost of potato chips here in Canadian dollars and cents... Enter the number of dollars per bag of chips: 2 Enter the number of cents per bag of chips: 0 Okay. Enter the number of bags would you like to purchase: 0 Okay! Thanks for chatting about our beautiful weather. ~~~ The vending machine interaction just ends with that "`beautiful weather`" message at the end. --- **A Transaction Where Chips Cost Nothing** If they ask for *some* number of bags of chips, but the chips cost nothing, then the vending machine does not ask them to insert any money. Instead, it reports the total price of `$0.00` and ends the transaction by telling them to enjoy the flavor of chips they chose. As an example, the script should behave like what's below: ~~~ none |||| |||| |||| _/TT\_ |||| |||| \\||// |||| |||| '-||-' |||| |||| |||| Welcome to Canada! ------------------ Enter the current temperature in whole degrees Celsius: 30 That means that it is 86 degrees Fahrenheit outside. Let's get the cost of potato chips here in Canadian dollars and cents... Enter the number of dollars per bag of chips: 0 Enter the number of cents per bag of chips: 0 Okay. Enter the number of bags would you like to purchase: 4 Enter the chip flavor you prefer [plain, pickle, or ketchup]: pickle Your total is $0.00 Canadian. Enjoy your free pickle chips! ~~~ --- **A Transaction Where They Enter Some Other Flavor** In cases where they request some bags of chips, they might enter a different flavor than the three requested. If they enter something other than `plain`, `pickle`, and `ketchup` then it should output a line immediately below `Enter the chip flavor...` that says ~~~ Sounds tasty, but we can only offer you plain chips. ~~~ The remaining vending interaction should proceed as if they had entered `plain`, even though they entered some fourth flavor instead. Here is an interaction where they enter `ranch` when the bags of chips cost $2.20 each: ~~~ none |||| |||| |||| _/TT\_ |||| |||| \\||// |||| |||| '-||-' |||| |||| |||| Welcome to Canada! ------------------ Enter the current temperature in whole degrees Celsius: 30 That means that it is 86 degrees Fahrenheit outside. Let's get the cost of potato chips here in Canadian dollars and cents... Enter the number of dollars per bag of chips: 2 Enter the number of cents per bag of chips: 20 Okay. Enter the number of bags would you like to purchase: 4 Enter the chip flavor you prefer [plain, pickle, or ketchup]: ranch Sounds tasty, but we can only offer you plain chips. Your total is $8.80 Canadian. Please give me 4 toonies and 1 loonie. [hit RETURN]: Thank you! Here are your plain chips. ~~~ And below is an example where the chips were free, but they entered `bbq` as the flavor. ~~~ none |||| |||| |||| _/TT\_ |||| |||| \\||// |||| |||| '-||-' |||| |||| |||| Welcome to Canada! ------------------ Enter the current temperature in whole degrees Celsius: 30 That means that it is 86 degrees Fahrenheit outside. Let's get the cost of potato chips here in Canadian dollars and cents... Enter the number of dollars per bag of chips: 0 Enter the number of cents per bag of chips: 0 Okay. Enter the number of bags would you like to purchase: 4 Enter the chip flavor you prefer [plain, pickle, or ketchup]: bbq Sounds tasty, but we can only offer you plain chips. Your total is $0.00 Canadian. Enjoy your free plain chips! ~~~ (##) `[HW3 P2]` feet to meters There are three feet in a yard and a yard is equal to 0.9144 meters. In a file called `feet_to_meters.py` define a function `feet_to_meters` that takes a value that represents a length in feet. The function should calculate and return a value that is the equivalent length in meters. It should calculate this conversion using floating point numbers. This means that this is the kind of code you should expect to write in `feet_to_meters.py`: ~~~ python def feet_to_meters(f): ~~~ Here, we are using `f` to represent a number that gets fed to the `feet_to_meters` function. In the lines that follow you should have a line of code, or maybe several lines of code, that perform the conversion calculation. That code should work for any floating point value of `f`. Again that code should be *indented* and should ultimately `return` the value it calulates. It shouldn't contain any `print` or `input` statements within its definition. Here is an example test of that function within the Python interpreter: ~~~ none jimfix@C02F48U1ML7H homework3 % python3 -i feet_to_meters.py >>> feet_to_meters(10.0) 3.048 ~~~ Note that, because of the nature of floating point, it is possible to get a *slightly* different result than the output above. That could happen because you used a different formula than we did. When you submit your code our tests will tolerate slight differences in the numeric value, but will not tolerate significant differences. Let me once again point out that your `feet_to_meters` function should *return* the converted value rather than *output* it using `print`. Printing values is very different than returning values. Ask us about this if you are unclear about this distinction. Some of our testing won't allow printing to happen within a function's code. (##) `[HW3 P3]` coins In a file `coins.py`, write a function `coins` that, when given a number of cents, prints the number of US coins (quarters, dimes, nickels, pennies) needed to make that amount. For example it should run like so ~~~ python jimfix@C02F48U1ML7H homework3 % python3 -i coins.py >>> coins(11) 2 ~~~ because only two coins, a dime and a penny, are needed to make change of 11 cents. Here are a few more examples: ~~~ python >>> coins(25) 1 >>> coins(117) 8 >>> coins(112) 7 ~~~ (##) `[HW3 P4]` stars The US is aware of Canada's new vending technology. They are prototyping some Python code to produce "star fields" as [ASCII art](https://en.wikipedia.org/wiki/ASCII_art). For example, the text below is made up of `50` asterisks: ~~~ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ~~~ The console art above is 11 characters wide and 9 lines tall. The lines alternate between a mix of asterisks and spaces, and spaces and asterisks. Here is a version that is 4 characters wide (the first and last lines end with a space) and 3 lines tall: ~~~ * * * * * * ~~~ This one is 8 characters wide and 4 lines tall: ~~~ * * * * * * * * * * * * * * * * ~~~ Towards this end, write a function `stars` in a file named `stars.py` that takes an integer `width` and integer `height`. It should return a string that, when printed, produces a star field of that number of characters wide, and that number of lines tall. For example, you would want this Python interaction when loaded with `stars.py`: ~~~ Python >>> stars(6,4) '* * * \n * * *\n* * * \n * * *\n' >>> s = stars(6,4) >>> print(s) * * * * * * * * * * * * >>> ~~~ Notice that we make each line separated by the new line character`\n` be of the same length by using spaces judiciously. You can assume that both `width` and `height` are positive integers. (##) `[HW3 P5]` th Write a function `def th(rank)` that takes a non-negative integer `rank` and returns a string giving the ranking suffix of that integer. For example: ~~~ python >>> th(1) '1st' >>> th(2) '2nd' >>> th(3) '3rd' >>> th(4) '4th' ~~~ All numbers other than 1, 2, and 3 should have a suffix of `th`, including 0. This means that your function should have this behavior: ~~~ python >>> th(31) '31th' >>> th(32) '32th' >>> th(33) '33th' >>> th(34) '34th' ~~~ **There is a bonus exercise at the end that fixes this behavior.** (##) `[HW3 P6]` cheapest Alice and Bob each own a moving company. Each company charges its customers a fixed price for each trip of a truck that is required to move the customer’s belongings. Alice’s truck can hold 11 boxes and she charges $200 per trip. Bob’s truck can hold 14 boxes and he charges $250 per trip. Write a function `cheapest` that takes as input the number of boxes a customer has and returns a string equal to the name of the person whose company would be cheaper. If the two companies would charge the same amount, have the function choose Alice's over Bob's. For example, `cheapest(25)` returns `'Bob'` because Alice would charge $600 (three trips) and Bob would charge $500 (two trips), and so Bob charges less in that case. (##) `[HW3 P7]` print zap buzz You can also use `def` to define *procedures*. These are named lines of code that take parameters. But they *do something* rather than return a value. For example, here is a procedure that outputs `odd` or `even` depending on the value of its parameter `number`: ~~~ python def print_parity(number): if number % 2 == 0: print("even") else: print("odd") ~~~ Here is `print_parity` in use: ~~~ python >>> print_parity(2) even >>> print_parity(13) odd >>> ~~~ Write a procedure `def print_zap_buzz(number)` within a file named `print_zap_buzz.py` that either prints a line that says `zap`, prints a line that says `buzz`, or prints two lines with `zap` followed by ` buzz` in the cases it should for the parameter `number`. This means it should follow the rules from the exercise in Homework 2 Problem 6. If `number` doesn't meet the criteria for those cases, it should not do anything at all. Here it is in use: ~~~ python >>> print_zap_buzz(14) zap >>> print_zap_buzz(13) buzz >>> print_zap_buzz(8) >>> print_zap_buzz(35) zap buzz >>> ~~~ You can assume the integer `number` is positive and less than 1000. You might find it useful to write separate additional functions for the two conditions checked so as to determine whether `zap` and `buzz` should be output. (##) `[HW3 B8]` nd In the `[HW3 P5] th` exercise, the function we write isn't sophisticated enough to handle all the appropriate cases for putting a rank suffix after a number. In this **BONUS** exercise, you are asked to fix that. Write a function `def nd(rank):` that returns a string for a rank according to what's appropriate for all non-negative integers. For example: ~~~ python >>> nd(30) '31th' >>> nd(31) '31st' >>> nd(32) '32nd' >>> nd(33) '33rd' >>> nd(34) '34th' ~~~ And also: ~~~ python >>> nd(103) '103rd' >>> nd(2001) '2001st' >>> nd(4077) '4077th' >>> nd(2112) '2112th' >>> nd(100121) '100121st' >>> nd(100122) '100122nd' ~~~ Numbers whose last two digits are in the teens should end with `th`. In all other cases, we determine the rank suffix by the ones digit.