Program 4

Functions and Control Structures

ICS-31: Introduction to Programming


Introduction This programming assignment is designed to ensure that you know how to write functions that combine the standard control structures in Python: blocks, ifs, for loops, while loops, break, try/except, return, and raise statements. You will also continue gaining experience with the more basic Python features: assignment and import statements and expressions (that use arithmetic, relational, logical, and textual operators -including lots of logical/bool expressions).

In the first part of the assignment, you will write a variety of generally useful functions in the miscfunclib.py module; in the second part you will write functions in the tennis.py script, specific to a simulation for tennis.

Import whatever modules/functions you think appropriate, using whatever form of import you think appropriate (try to use both forms appropriately). Use short (but not overly short) well-chosen names. For indefinite loops, please write while True: loops with if/break statements in their blocks; when you are finished, you may transform the while test (from True to a real test) if the result is simpler.

You will download the program4 project folder, unzip it, move it to your workspace, create a project from it, and write the two required modules in this project folder. Submit each module separately in Checkmate. Only one student should submit all parts of the the assignment, but both student's names should appear in the comments at the top of each submitted .py file. It should look something like


# Romeo Montague, Lab 1
# Juliet Capulet, Lab 1
# We certify that we worked cooperatively on this programming
#   assignment, according to the rules for pair programming
Please turn in each script as you finish it, so that I can accurately assess the progress of the class as a whole during this assignment.

Print this document and carefully read it, marking any parts that contain important detailed information that you find (for review before you turn in the files). Reread the section on Time Management from Programming Assignment 0 before starting this assignment.


A Module of General Functions Write a module named miscfunclib that mirrors the library modules in the courselib folder: it contains the following functions, each of which might be useful in a variety of scripts. Note that the miscfunlib module itself contains some code to test these functions The module miscfunlibdriver (in the downloaded project folder) contains a special script that you can use to test your functions more generally. Test/debug each function after you write it.
  • majority defines three bool parameters; it returns whichever bool argument value occurs the most times. For example, majority(True,False,True) should return True; majority(True,False,False) should return False. Hint: I found it useful to use arithmetic operators on these parameters: recall bool values are promoted to int values: False is promoted to 0 and True is promoted to 1. My function has 2 lines.

  • constrain defines three numeric (int or float) parameters (name them low, x, and high); it raises the ValueError exception if low exceeds high, including an appropriate error message with the function name, a statement of the error, and the incorrect values of low and high. Otherwise, it returns x's value if it is in the range [low,high]; if it is outside the range it returns either low or high,whichever is closer to x. For example, constrain(0,5,10) should return 5; constrain(0,12,10) should return 10; constrain(0,-1,10) should return 0. My function has 9 lines.

  • prime_density defines two int parameters (name them low and high); it returns the ratio of the number of primes between these values (inclusive) divided by the number of values in the range. Unlike constrain if low exceeeds high in this function, swap these two values so low< is the lowests and high is the highest. For example, prime_density(2,20) should return 0.42105263157894735 (8/19: there are eight primes in this range -2, 3, 5, 7, 11, 13, 17, 19- and 19 values). Note that prime_density(2,20) returns the same result as prime_density(20,2). prime_density(100,199) should return 0.21 and prime_density(1000,1199) should return 0.14 and My function has 10 lines.

  • nth_prime_after defines two int parameters (name them v and n); it returns the nth prime found after v (not including v). For example nth_prime_after(17,4) should return 31 (19 is the first after, 23 is the second after, 29 is the third after, and 31 is the fourth after), and nth_prime_after(2,20) should return 73. Hint: included a defininte loop with an indefinite loop inside. First write a function that ignores n and uses and indefinite loop to compute the first prime coming after v; then put that code in the body of a definite loop that executes its body n times. My function has 7 lines.

  • count_lines defines one str parameter (name it file_name); it returns the number of non-blank lines in the specified file. If Python cannot find a file with that name, return 0. Hint: use a try/except statement. My function has 8 lines.

  • Extra Credit: justify defines two parameters, one str and one int (name them s and n); it returns a string that has all the words in s but justified so that every line has no more than n characters. For example, the miscfunclibdriver.py module contains the name ny_story bound to a long string. If we execute print(justify(ny_story,40)) it prints
             1         2         3         4
    1234567890123456789012345678901234567890
    John and Mary decided to take a walk in
    the park one day. Living in NY, of
    course the park was central park, so off
    they went. It was a fine spring day so
    they saw many fellow New Yorkers on
    their trip.
    If we execute print(justify(ny_story,20)) it prints
             1         2
    12345678901234567890
    John and Mary
    decided to take a
    walk in the park one
    day. Living in NY,
    of course the park
    was central park, so
    off they went. It
    was a fine spring
    day so they saw many
    fellow New Yorkers
    on their trip.
    Hint: use 3 local names: answer, start, and stop; use a double-nested while loop: the outer one determines if there are any more words in s to justify; the inner one determines the end of the next line to catenate into answer (remember to catenate a '\n' at the end of each line). Use lots of string indexing and some slicing. My function has 11 lines.

Testing

There are two ways to test these functions.
  1. The miscfunclib.py module itself includes the standard tests.examples whoen here. Recall, the code in the if is execute if we run this module as a script (not it it is imported by another module); call to pe prints the string (representing an expression) and then the evaluation of the string.

  2. The miscfunclibdriver.py module includes a loop that prompts/executes statements executed by the user. It imports all the function names from the miscfunlib.py module (and because this module is imported, its own tests don't run). There are two special functions that the user can call: each expects the name of a file that is in the project folder.
    • batch_test('miscfunclibtest.txt') executes every command in the file miscfunlibtest.txt, which I have set up to duplicate the tests done in miscfunlib.py.

    • batch_self_check('miscfunclibselfcheck.txt') evaluates every expression that comes before -> and verifies that it produces the result that comes after the ->. Again, I have set up this file to duplicate the tests done in miscfunlib.py.
    Note that if confirm is bound to True these functions wait for the user to confirm the proecssing of each line in the file. You can useboth of these modules for testing, adding appropriate tests to the .txt files.


Tennis Write a script that defines various functions that simulate different aspects of Tennis: points, games, sets, and matches; these functions will build on top of each other: e.g., the points function will help simulate games, the games function will help simulate sets, and the sets function will help simulate matches.

Our goal is to investiate the following issue: Given the probability for which player A beats player B on a single point, what is the probability that player A wins a match from player B, using the standard rules (scoring) for tennis?

For example, if the probability that player A beats player B on a single point is 50%, we would expect each player to win about 50% of the matches. How much better, per point, would player A have to be over player B, for player A to win win 90% of the matches? Would he/she have to be a bit better (win a single point with probability 51%) or have to be a lot better (win a single point with probability 90%)? Generally, what is the relationship between the probability of winning a single point and the probability of winning an entire match (composed of sets, composed of games, composed of points)?

Once we have investigated this issue, we can also investigate the same question for different rules in tennis: by changing our functions/script we can easily investigate changes like adjusting

  • the minium number of points to win a game, games to win a set, or sets to win a match.

  • the number of points/games/sets needed to be ahead (always 2 in tennis) to win a game/set/match.
All these rule changes would require us to change the simulation in a minor way (by changing some numbers in our script); for radically different rules, we would have to revise the Python code in our functions/script more extensively.

Here are a summary of the rules of tennis: how to win games, sets, and matches (which are actually all similar).

  • Games consisist of points won by each player: to win a game, a player must have won at least 4 points, and have won at least 2 more points than the other player.

  • Sets consisist of games won by each player: to win a set, a player must have won at least 6 games, and have won at least 2 more games than the other player.

  • Matches consisist of sets won by each player: to win a match, a player must have won at least 3 sets, and have won at least 2 more sets than the other player.
Write your functions/script in the tennis.py module.
  1. Write a for loop using frange function, already written in the tennis.py module. It should print all the values from .45 to .55 inclusive, in step of .01. Some of these numbers may print a bit funny (don't worry about that now). Put this code in a comment and leave it at the bottom of the tennis.py module (writing the code specified below in between).

  2. Define the function point_winner with the header
    point_winner(percent_A_win_point)
    This function returns either 'A' or 'B' as the winner of one point in a tennis game, given that percent_A_win_point's argument will be a float in the range [0.0,1.0] where 0.0 means A wins 0% of the points (never) and 1.0 means A wins 100% of the points (always); if percent_A_win_point is .50 it means A wins 50% of the points (half).

    To simulate a point, we will use the rand function in the random module. Its header is random.random() -> float: (yes, a parameterless function). When called, it returns a random float in the range [0.0,1.0] inclusive, such that each value in this range is equally likely. So random.random is parametrless because it always returns a result in the same range.

    This function should return 'A' if the random number is <= percent_A_win_point, and 'B' otherwise. For example, if percent_A_win_point is .75, it means that 75% of the random numbers generated will have a value <= percent_A_win_point, so this function will return 'A' 75% of the time.

  3. Define the function A_win_stats with the header
    A_win_stats(percent_A_win_point, times_to_play, trace=False)
    Primarily this function calls the point_winner function times_to_play times and returns the percentage of points that A wins. If trace is True it also prints out the details of each point. So, if we executed the following script after defining the function
    result = A_win_stats(.50, 10, True)
    print('A won',str(result)+'%')
    The console might contain (might, because random number are invovled) the following trace and final print.
    Playing 10 points with percent_A_win_point = 0.50
      point 1 winner = B
      point 2 winner = B
      point 3 winner = A
      point 4 winner = B
      point 5 winner = B
      point 6 winner = A
      point 7 winner = A
      point 8 winner = A
      point 9 winner = A
      point 10 winner = A
    A won 60.0 percent of the points
    A won 60.0%
    Notice that all lines but the last are printed by the A_win_stats function; the last is printed by the second statement above. If instead our script specified trace=False, it would print only
    A won 60.0%
    So admittedly this function is strange because it primarily computes a value, but optionally it outputs information to the console, tracing its actions.

    Define the function A_win_stats and use the script above to test it with different perecentages for A winning, both with tracing on and off. With tracing off, play thousands or millions of points and observe that the percentage of time A wins is very close to the argument passed to the first parameter of A_win_stats. Hint: my function is 13 lines long

  4. Define the function game_winner with the header
    game_winner(percent_A_win_point)
    This function simulates a full game of tennis by calling point_winner repeatedly, until one player wins (according to the rules of tennis above): the function returns 'A' or 'B'. Hint: keep track of the number or points won by each player; write an indefinite loop that terminates when one play has won the game according to the rules; return the letter for that player. While debugging this function you might print the winner of each point, to see that the function is following the scoring rules correctly; later you can comment-out this printing. My function is 12 lines long.

  5. Make a copy of your original A_win_stats function and comment out one version. Modify the uncommented-out version so that it calls game_winner and for the following script
    result = A_win_stats(.50, 10, trace=True)
    print('A won',str(result)+'%')
    produces the following output on the console. Notice that some of the words have changed.
    Playing 10 games with percent_A_win_point = 0.5
      game 1 winner = B
      game 2 winner = A
      game 3 winner = B
      game 4 winner = B
      game 5 winner = A
      game 6 winner = B
      game 7 winner = B
      game 8 winner = A
      game 9 winner = B
      game 10 winner = A
    A won 40.0 percent of the games
    A won 40.0%

  6. Rewrite the function A_win_stats so that it can be used to call either the point_winner or game_winner function and print the right information point/points or game/games. Generalize it header to
    A_win_stats(percent_A_win_point, play_function, times_to_play, trace=False, unit=None, suffix='s')
    Doing so allows us to call A_win_stats in many different ways:
    result = A_win_stats(.50, point__winner, 10, True, 'point','s')
    result = A_win_stats(.50, game_winner, 10, True, 'game')
    result = A_win_stats(.50, game_winner, 1000)
    The first calls the point_winner function 10 times with tracing, printing point/points. The second calls the game_winner function 10 times with tracing, printing game/games (with suffix binding to its default argument 's'). The third calls the game_winner function 1000 times with no tracing: the last three parameters bind to their default values, and because tracing binds to False the other two parameters are never used.

    After rewriting your A_win_stats function, ensure each of these three function calls works as described above.

  7. Define the function set_winner with the header
    set_winner(percent_A_win_point)
    This function simulates a full set of tennis by calling game_winner repeatedly, until one player wins (according to the rules of tennis above): the function returns 'A' or 'B'. Hint: set_winner and game_winner have identical structure; it should be simple to copy/paste game_winner and then modify it to define set_winner.

    Write a script to call A_win_stats and play 10 sets with tracing on, and then play 1,000 sets with tracing off.

  8. Define the function match_winner with the header
    match_winner(percent_A_win_point)
    This function simulates a full set of tennis by calling set_winner repeatedly, until one player wins (according to the rules of tennis above): the function returns 'A' or 'B'. Hint: match_winner and set_winner and game_winner have identical structure; it should be simple to copy/paste one of these functions and then modify it to define match_winner

    Write a script to call A_win_stats and play 10 matches with tracing on, and then play 1,000 matches with tracing off.

  9. Write a script at the bottom of this module that prompts the user for a range of winning percentages for player A (a minimum, maximum, and step) and the number of matches to play, and computes and tabulates the results showing how the differences in the percentage of winning a point, compound to produce the differences in the percentages of winning a match.

Here is an example of my final script running. I supplied default values to the prompting functions (they automatically appear in brackets) and just pressed enter to use those default values when prompted. Of course because we are using random numbers, your answer may be different. What is surprising is how senstiive winning matches is to winning points: a slightly better percentage for winning points translates into a much lareger percentage for winning matches.
Enter minimum percentage[0.45]: 
Enter maximum percentage[0.55]: 
Enter percentage step[0.01]: 
Enter matches to play[10000]: 
with percent_A_win_point = 45.0% A wins  2.3% of the matches
with percent_A_win_point = 46.0% A wins  5.7% of the matches
with percent_A_win_point = 47.0% A wins 10.9% of the matches
with percent_A_win_point = 48.0% A wins 20.2% of the matches
with percent_A_win_point = 49.0% A wins 33.9% of the matches
with percent_A_win_point = 50.0% A wins 50.1% of the matches
with percent_A_win_point = 51.0% A wins 65.8% of the matches
with percent_A_win_point = 52.0% A wins 79.2% of the matches
with percent_A_win_point = 53.0% A wins 88.7% of the matches
with percent_A_win_point = 54.0% A wins 94.2% of the matches
with percent_A_win_point = 55.0% A wins 97.3% of the matches
An upcoming lecture (before the project is dues) will cover printing float values nicely.