CS303E Homework 3

Instructor: Dr. Bill Young
Due Date: Monday, February 3, 2025 at 11:59pm

Copyright © William D. Young. All rights reserved.

I adapted this assignment from one by my colleague Dr. Bill Bulko who earlier in his life was a professional bowler!

Background

Bowling is the world's number one participation sport! It is the only sport that takes place on all seven continents, including McMurdo Station in Antarctica!

As a way of making bowling leagues more fun for beginners, bowlers are often assigned a handicap so that beginning bowlers may compete more evenly with experienced bowlers. The handicap is calculated based on the bowler's average; the higher the average, the lower the handicap. For example, a bowler with an average of 100 may have a handicap of 80, and a bowler with an average of 150 may only have a handicap of 40. When the two bowlers compete against each other, their handicaps are added to their respective scores to determine the winner. The higher bowler still has an advantage (if they both bowl their averages exactly, the 150 + 40 = 190 will beat the 100 + 80 = 180) but the match is clearly much closer. If the 150 bowler bowls exactly 150, the 100 bowler need only bowl a 111 to win instead of a 151. You can assume that the scores entered are integers and that they are legal bowling scores, i.e., in the range [0..300].

Algorithm

Every bowling league may define the way handicap is calculated with their own methodology. However, most typical leagues use a formula such as the following:
       handicap = (200 - average) * 80%
   
Here the bowler's average (a floating-point number) is truncated to an integer, and the handicap is also truncated to an integer. (You can use the int() function to truncate a positive floating point number to an integer.) However, if the computation would result in a negative handicap, report the handicap as 0. Note that multiplying by 80% really means multiplying by 0.8.

To report a negative handicap as 0, one would naturally consider an if statement. But since we haven't introduced that concept you can do the following. Compute the handicap in, say, a variable handicap. Then do this:

handicap = max( 0, handicap )
Think about why that works.

For example: if a bowler in the above league had a 147.8 average, you would calculate her handicap as follows:

       average = 147 (147.8 truncated)
       handicap = (200 - average) * 80%
                = (200 - 147) * 80%
                = 53 * 80% = 42.4
   
which is then truncated to 42.

On the other hand, an excellent bowler with an average of 225.5 would have handicap computed as follows:

       average = 225 (225.5 truncated)
       handicap = (200 - average) * 80%
                = (200 - 225) * 80%
                = -25 * 80% = -20
   
But we don't allow negative handicaps, so this is replaced by 0. Note that as soon as we see that the average is greater than or equal to 200, we can stop the computation and report 0.

Assignment and Expected Output:

Write a complete Python program that calculates a bowler's average and handicap after three games. Using input statements, prompt for the user's first name and the scores of three bowling games (integers between zero and 300 inclusive) one at a time. Then calculate the bowler's average and handicap using the formula above. Print out a nicely formatted report of the bowler's average and handicap, truncated to integers. See the examples below for the format.

The sample below shows three separate runs of the program. (Your code only has to handle one bowler, not three! But, of course, you can run your program as often as you like.) Format your output so that, if you were to use the same inputs, your output would match the following exactly. Notice that the handicap may be low, zero, but not negative. There should be one space after each colon (where something follows on the line) and a single blank line above and below the output in addition to the blank lines internal to the report. There are two spaces at the start of the average and handicap lines. Follow these examples and you'll be fine.

> python Handicap.py

Enter bowler's name: Bernard
Enter Game 1: 200
Enter Game 2: 184
Enter Game 3: 209

Handicap report for Bernard:
  Bernard's average is: 197
  Bernard's handicap is: 2

It's time to Bowl!

> python Handicap.py

Enter bowler's name: Cindy Lou
Enter Game 1: 50
Enter Game 2: 82
Enter Game 3: 75

Handicap report for Cindy Lou:
  Cindy Lou's average is: 69
  Cindy Lou's handicap is: 104

It's time to Bowl!

> python Handicap.py

Enter bowler's name: Tom Daugherty
Enter Game 1: 300
Enter Game 2: 295
Enter Game 3: 287

Handicap report for Tom Daugherty:
  Tom Daugherty's average is: 294
  Tom Daugherty's handicap is: 0

It's time to Bowl!

> 
The final example in slideset 3 shows how to use the name in the later output. Ideally, your program shouldn't crash regardless of what values are input. However, your program does not need to verify that the numbers input are legal bowling scores; we won't test your program for illegal inputs.

Turning in the Assignment:

The program should be in a file named Handicap.py. Submit the file via Canvas before the deadline shown at the top of this page. Submit it to the assignment hw3 under the assignments sections by uploading your python file.

Be sure to test your program before you submit it. It should also contain a header with the following format:

# File: Handicap.py
# Student: 
# UT EID:
# Course Name: CS303E
# 
# Date:
# Description of Program: 

If you submit multiple times to Canvas, it will rename your file name to something like Handicap-1.py, Handicap-2.py, etc. Don't worry about that; we'll grade the latest version.

Programming Tips:

Extra white space in inputs: Try running your program with extra whitespace around the user inputs and see what happens. For the integer inputs, it probably won't make any difference; when you apply int() it ignores extra white space. For string values (like the name) it will matter; spaces are just more characters that are part of the string input. Later in the semester, we'll learn the method strip() to remove leading and trailing whitespace from an input (or any string).
>>> s = input("name: ")
name:     Susie Q.   
>>> s
'    Susie Q.   '
>>> s.strip()                  # creates a new string, doesn't change s
'Susie Q.'
>>> n = input("value: ")
value:     12      
>>> n
'    12      '
>>> int(n)                     # creates an int, but doesn't change n
12
>>> 

Keep an eye on efficiency: In general, this semester we won't care much about the efficiency of our code. But it's a very important issue in computing. For example, when checking whether the computed handicap is negative, we could stop as soon as we see that the average is greater than or equal to 200. Or we go on with the computation and check whether the final computed handicap is less than zero. This is an obvious place to use an if statement (which we'll introduce next week). But for now, just compute the handicap and take the max of the 0 and the handicap. A few extra steps of computation don't really make much difference (in this case); but it's a good idea to favor more efficient computations when possible.

There's an entire area of computing called asymptotic complexity that looks at the efficiency of algorithms. You characterize the complexity of an algorithm by the number of "computational steps" it takes to process a set of data. For example, if you're searching for the smallest element in a list, you only need to look through the elements once, keeping track of the smallest you've seen so far. This is called a linear or "order n" algorithm. If you double the size of the list, you double the time (on average).

Later in the semester, we'll look at some sorting algorithms: bubble sort and insertion sort. These are quadratic or "order n2" algorithms; for each new element you add you have to look through the entire list again. So sorting using these algorithms takes approximately n2 computation steps. That means that a list that is twice as long requires four times as many steps to sort. A list that is ten times as long requires one hundred times as many steps to sort. You can see why a linear algorithm would be much more efficient than a quadratic one.

Some algorithms are exponential or "order kn", for some k. An example is the famous traveling salesman problem: given a list of cities and the distances between them, find the shortest route a salesman would have to take to visit all of them exactly once and return to the starting point. It's believed that this can't be solved in less than 2n steps, where n is the number of cities. Think about how big that number is for 100 cities.

The following table shows why this all matters. The left column is the "order" of the algorithm, meaning the number of steps as a function of the size of the input set. We're supposing each computation takes a microsecond (one millionth of a second) and seeing how long it takes to compute the output as the size of the input set increase (across the top). Now imagine how each algorithm would perform for thousands or millions of elements.

n = 10n = 20n = 30n = 40n = 50n = 60
n.00001 sec.00002 sec.00003 sec.00004 sec.00005 sec.00006 sec
n2 .0001 sec.0004 sec.0009 sec.0016 sec.0025 sec.0036 sec
n3 .001 sec.008 sec.027 sec.064 sec.125 sec.216 sec
n5 .1 sec3.2 sec24.3 sec1.7 min5.2 min13.0 min
2n .001 sec1.0 sec17.9 min12.7 days35.7 yr366 cen
3n .059 sec58 min6.5 yr3855 cen2 * 108 cen1.3 * 1013 cen

You can see why it's often important to pick the most efficient algorithm to solve a problem. Later in the semester, we'll look at a specific problem (finding the nth Fibonnaci number) for which there is an exponential solution, a linear solution, and even a constant time solution (the time to compute doesn't depend on n).