Problem Set 2: Program flow, functions, and decisions

Preliminaries

In your work on this assignment, make sure to abide by the collaboration policies of the course.

For each problem in this problem set, we will be writing or evaluating some Python code. You are encouraged to use the Spyder IDE which will be discussed/presented in class, but you are welcome to use another IDE if you choose.

If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.

Programming Guidelines

  • Refer to the class Coding Standards for important style guidelines. The grader will be awarding/deducting points for writing code that comforms to these standards.

  • Every program file must begin with a descriptive header comment that includes your name, username/BU email, and a brief description of the work contained in the file.

  • Every function must include a descriptive docstring that explains what the function does and identifies/defines each of the parameters to the function.

  • Your functions must have the exact names specified below, or we won’t be able to test them. Note in particular that the case of the letters matters (all of them should be lowercase), and that some of the names include an underscore character (_).

  • Make sure that your functions return the specified value, rather than printing it. None of these functions should use a print statement.

  • If a function takes more than one input, you must keep the inputs in the order that we have specified.

  • You should not use any Python features that we have not discussed in class or read about in the textbook.

  • Your functions do not need to handle bad inputs – inputs with a type or value that doesn’t correspond to the description of the inputs provided in the problem.

  • You must test your work before you submit it You can prove to yourself whether it works correctly – or not – and make corrections before submission. If you need help testing your code, please ask the course staff!

  • Do not submit work with syntax errors. Syntax errors will cause the Gradescope autograder to fail, resulting in a grade of 0.

Warnings: Individual Work and Academic Conduct!!

  • This is an individual assignment. You may discuss the problem statement/requirements, Python syntax, test cases, and error messages with your classmates. However, each student must write their own code without copying or referring to other student’s work.

  • It is strictly forbidden to use any code that you find from online websites including but not limited to as CourseHero, Chegg, or any other sites that publish homework solutions.

  • It is strictly forbidden to use any generative AI (e.g., ChatGPT or any similar tools**) to write solutions for for any assignment.

Students who submit work that is not authentically their own individual work will earn a grade of 0 on this assignment and a reprimand from the office of the Dean.

If you have questions while working on this assignment, please post them on Piazza! This is the best way to get a quick response from your classmates and the course staff.



Problem 1: Program flow and variable scope

10 points

Try to do this part before workshop time!

Begin by downloading this text file: ps2pr1.txt.

Rick-click on the link, and use “Save As...” to save the text file to your computer.

Edit this file, and save your work in this part into that file.

  1. Consider the following Python program:

    x = 11
    y = 5
    x = x + 6
    z = y + x
    x = x // 7
    y = z % 3
    

    Copy the table shown below into your text file for this problem, and fill in the missing rows/values to illustrate how the values of the variables change over the course of the program. We’ve given you the values of the variables after the first two lines of code. You should complete the remaining rows of the table.

    line of code   | x  | y  | z  |
    --------------------------------
    x = 11         | 11 |    |    |
    y = 5          | 11 | 5  |    |   
    x = x + 6      |    |    |    |
    z = y + x      |    |    |    |
    x = x // 7     |    |    |    |
    y = z % 3      |    |    |    |
    
  2. Consider the following Python program:

    def foo(a, b):
        b = b - 2
        a = a - b
        print('foo', a, b)
        return a
    
    a = 5
    b = 3
    print(a, b)
    a = foo(a, b)
    print(a, b)
    foo(b, a)
    print(a, b)
    

    In section 2-2 of ps2pr1.txt, we have given you tables like the ones shown below:

    global variables (ones that belong to the global scope)
      a  |  b  
    -----------
      5  |  3     
         |
    
    local variables (ones that belong to foo)
      a  |  b  
    -----------
         |       
         |
    
    output (the lines printed by the program)
    ------
    5 3
    

    We have started the first and third tables for you. You should:

    • complete the first and second tables so that they illustrate how the values of the variables change over time
    • complete the third table so that it shows the output of the program (i.e., the values that are printed).

    You may not need all of the rows provided in the tables.

  3. Consider the following Python program:

    def wow(a):
        b = a * 2
        print('wow:', a, b)
        return b
    
    def yay(b):
        a = wow(b) + wow(b + 2)
        print('yay:', a, b)
        return a
    
    a = 4
    b = 3
    print(a, b)
    b = wow(b)
    print(a, b)
    yay(a)
    print(a, b)
    

    In section 2-3 of ps2pr1.txt, we have given you tables like the ones shown below:

    global variables (ones that belong to the global scope)
      a  |  b  
    -----------
      4  |  3     
         |
    
    wow's local variables
      a  |  b  
    -----------
         |       
         |
    
    yay's local variables
      a  |  b  
    -----------
         |       
         |
    
    output (the lines printed by the program)
    ------
    4 3
    

    We have started the first and fourth tables for you. You should:

    • complete the first three tables so that they illustrate how the values of the variables change over time
    • complete the last table so that it shows the output of the program (i.e., the values that are printed).

    You may not need all of the rows provided in the tables.


Problem 2: Functions on strings and lists

30 points

Begin this part before workshop time!

In Spyder, use the File -> New File menu option to open a new editor tab for your program, and save it using the the name ps2pr2.py. Make sure to specify the .py extension.

Include comments at the top of the file that are similar to the ones that we gave you at the top of ps1pr3.py.

  1. Write a function first_and_last(values) that takes a list values and returns a new list containing the first value of the original list followed by the last value of the original list. You may assume that the original list has at least one value.

    Although this function could be completed in one line, you must use the following template:

    def first_and_last(values):
        """ put your docstring here """
        first = ________ 
        last = ________
        return [first, last]
    

    Here are some examples of how the function should work:

    >>> first_and_last([1, 2, 3, 4])
    [1, 4]
    >>> first_and_last([7])        # 7 is both first and last!
    [7, 7]
    >>> first_and_last(['how', 'are', 'you?'])
    ['how', 'you?']
    

    Warning

    Remember that your functions should return the correct value rather than printing it. If your function is printing the return value, you will see the word None as part of the output for the last test above. If you are seeing None in your output, you must fix your function so that it uses a return statement rather than a print statement. This same warning applies to all of the functions that you will write for this assignment, with the exception of the optional test functions that you may include for testing your code.

  2. Write a function move_to_end(s, n) that takes as inputs a string value s and an integer n, and that returns the a new string in which the first n characters of s have been moved to the end of the string. For example:

    >>> move_to_end('computer', 3)    # move 'com' after 'puter'
    'putercom'
    >>> move_to_end('computer', 5)    # move 'compu' after 'ter'
    'tercompu'
    >>> move_to_end('computer', 0)
    computer
    

    Special case: If n (the second input) is greater than or equal to the length of s (the first input), then the function should simply return the original s without changing it:

    >>> move_to_end('hi', 5)    # 5 > len('hi')
    'hi'
    
  3. Write a function truncate(s, max_length) that takes a string parameter s and an integer parameter max_length, and returns the first max_length characters of s. Use the slicing operator

    Here are some examples:

    >>> truncate('the long and winding road', 12)
    'the long and'
    >>> truncate('get back', 12) # fewer than 12 characters in s;
    'get back'
    

    Note that when s is shorter than max_length, the entire string s is returned.

  4. Write a function triple_outsides(s) that takes a string s, and returns a version of that string with the first and last characters repeated 3 times, and the rest of the characters in the middle occuring only once each.

    >>> triple_outsides('cayenne')
    'cccayenneee'
    >>> triple_outsides('')
    ''
    
  5. Write a function every_other(s) that takes a string s and returns a version of that string with every other character has been skipped.

    >>> every_other('abcde')
    'ace'
    >>> every_other('across the universe')
    'ars h nvre'
    

    Hints: * use indexing when you need an individual character * use the slicing operator to break the string into parts * use separate variables for each part

  6. Write a function rotate(s, n) that takes a string s and an integer n, and returns a new string in which each character has been moved n positions to the left. Characters that “fall off” the beginning of the left should be concatenated to the right (end) of the resulting string.

    >>> rotate('abcdef', 1)
    'bcdefa'
    >>> rotate('obladi, oblada', 2)
    'ladi, obladaob'
    

    Hint: * use the slicing operator to break the string into parts * use separate variables for each part

  7. Write a function mirror(s) that takes as input a string s and returns a mirrored version of s that is twice the length of the original string. For example:

    >>> mirror('bacon')
    'baconnocab'
    >>> print(mirror('XYZ'))
    XYZZYX
    

    Hint: Use skip-slicing!

  8. Write a function replace_end(values, new_end_vals) that takes as inputs a list values and another list new_end_vals, and that returns a new list in which the elements in new_end_vals have replaced the last n elements of the list values, where n is the length of new_end_vals. For example:

    >>> replace_end([1, 2, 3, 4, 5], [7, 8, 9])     
    [1, 2, 7, 8, 9]
    

    In the above example, the second input has a length of 3, so we replace the last 3 elements of the first input (the elements [3, 4, 5]) with the elements specified by the second input ([7, 8, 9]).

    Other examples:

    >>> replace_end([1, 2, 3, 4, 5], [10, 11])
    [1, 2, 3, 10, 11]
    >>> print(replace_end([1, 2, 3, 4, 5], [12]))
    [1, 2, 3, 4, 12]
    

    Special case: If the length of the second input is greater than or equal to the length of the first input, then the function should simply return the original second input:

    >>> replace_end([0, 2, 4, 6], [4, 3, 2, 1])
    [4, 3, 2, 1]
    >>> replace_end([0, 2, 4, 6], [4, 3, 2, 1, 0])
    [4, 3, 2, 1, 0]
    

    Hint: Begin by computing the length of new_end_vals and storing it in an appropriately named variable. Then use that variable in the rest of your code.

  9. Write a function repeat_elem(values, index, num_times) that takes as inputs a list values, an integer index (which you may assume is a valid index for one of the elements in values), and a positive integer num_times, and that returns a new list in which the element of values at position index has been repeated num_times times. For example:

    >>> repeat_elem([10, 11, 12, 13], 2, 4)   
    [10, 11, 12, 12, 12, 12, 13]
    

    In the above example, the second input is 2 and the third input is 4, so we take the element at position 2 of the list (the 12) and repeat it 4 times.

    Other examples:

    >>> repeat_elem([10, 11, 12, 13], 2, 6)    # repeat element 2 six times
    [10, 11, 12, 12, 12, 12, 12, 12, 13]
    >>> print(repeat_elem([5, 6, 7], 1, 3))    # repeat element 1 three times
    [5, 6, 6, 6, 7]
    

Don’t forget to test your functions. You may also find it helpful to use the Python Tutor visualizer to trace through the execution of your functions.


Problem 3: Functions with Decision Statements

20 points

In Spyder, use the File -> New File menu option to open a new editor tab for your program, and save it using the the name ps2pr3.py. Make sure to specify the .py extension.

Include comments at the top of the file that are similar to the ones that we gave you at the top of ps1pr4.py.

In this problem, you will write functions that use decision statements in producing their result.

  1. Write a function my_min(a,b) that takes 2 parameters and returns the minimum of these two. Inside your function, use decision logic to identify and return the minimum value among the parameters. Test your function using these statements at the bottom of your file. For example:

    >>> my_min(1,2)
    1
    >>> my_min(2,1)
    1
    

    Your function must use conditional execution, and it should not use Python’s built-in min function. You may use other built-in functions as needed.

  2. Write a function find_min(a, b, c) that takes 3 parameters and returns the minimum of these three. Inside your function, use decision logic to identify and return the minimum value among the parameters.

    >>> find_min(1,2,3)
    1
    >>> find_min(3,1,2)
    1
    >>> find_min(-3,-1,-2)
    -3
    

    Write test cases at the bottom of your file to test all possible permutations of the parameters, e.g., minimum in the first parameter (1,2,3), minimum in the middle parameter (2,1,3), minimum in the third parameter (3,2,1), as well as multiple parameters with the same minimum (e.g., 1, 1, 2), etc.

    You will need eight test cases to ensure it works correctly.

    Your function must use conditional execution, and it should not use Python’s built-in min function. You may use other built-in functions as needed.

  3. Write a function longer_len(s1, s2) that takes as inputs two string values s1 and s2, and that returns the length of the longer string. For example:

    >>> longer_len('computer', 'compute')
    8
    >>> longer_len('hi', 'hello')
    5
    >>> print(longer_len('begin', 'on'))
    5
    

    Your function must use conditional execution, and it should not use Python’s built-in max function. You may use other built-in functions as needed.

  4. Write a function is_mirror(s) that takes as input a string s and returns True if s is a mirrored string (i.e., a string that could have been produced by your mirror function) and False otherwise. Examples:

    >>> is_mirror('baconnocab')
    True
    >>> print(is_mirror('baconnoca'))
    False
    

    Warning

    Your function should return a boolean value – either True or False, without any quotes around them. If you see quotes around the result when you make the first call above from the Shell, you must fix your function to remove the quotes.

    Hints:

    • You may find it helpful to compute the value len(s) // 2 as part of this function.
    • You may also find it helpful to call your previous mirror function, although doing so is not required.

Problem 4: Validating Data

30 points

In Spyder, use the File -> New File menu option to open a new editor tab for your program, and save it using the the name ps2pr4.py. Make sure to specify the .py extension.

Include comments at the top of the file that are similar to the ones that we gave you at the top of ps1pr4.py.

In this part of the assignment, you will use decision statements to validate some parameter data, to determine whether the parameters can be interpreted as a valid calendar date.

For example:

    5/24/2008 is a valid date
    9/31/2018 is not a valid date, because day 31 is not valid date for September 2018.
    2/29/2000 is a valid date
    2/29/2100 is not valid, because day 29 is not valid date for February 2100.

Decomposing the Problem

You will write several helper functions to decompose this problem, and then use these helper functions to implement the complete solution. It is strongly recommended that you write each function in the order presented here, and test each one thoroughly before moving on to the next function.

The first 4 functions will NOT produce any output (print) statements, but rather only produce a result (return value). You may use print statements in these functions while writing/debugging them, but comment out the print statements from your final work.

Important Notes

  • Do not take user input from the keyboard (i.e., the input function) in any of your code for this assignment.

  • Hint: in each function, deal with the “invalid” cases first, and return False; a return statement always stops the function.
    At the end of the function, if nothing is invalid, you will return True.

  1. Write a function is_valid_month(month), where month is an integer parameter. The function returns True if the month is valid, and False otherwise. You will need at least three test cases to ensure it works correctly. For example:

    • Month 5 is valid.
    • Month -5 is not a valid month.
    • Month 15 is not a valid month.
  2. Write a function is_leap_year(year), where year is an integer parameter. The function returns True if the year is a leap year, and False otherwise.

    You may be familiar with the idea that every fourth year is a leap year (e.g., 2020, 2024, 2028, ...).

    In fact, the algorithm for selecting leap years is a bit more subtle. Here is a great explanation by Neil DeGrasse Tyson:

    Briefly:

    • Any year divisible by 400 is a leap year. (e.g., 2000, 2400)
    • Other centuries are not leap years (e.g., 2100 is not a leap year)
    • Other years divisible by 4 are leap years (e.g., 2020, 2024)
    • All other years are not leap years.

    You will need at least four test cases to ensure it works correctly.

  3. Write a function is_valid_day_in_month(month, day, year), where month, day, and year are integer parameters. The function returns True if the day number is valid within the month, and False otherwise. The rules are:

    • January, March, May, July, August, October and December have 31 days.
    • April, June, September, and November have only 30 days.
    • February has 28 days except in leap years when it has 29 days (hence the inclusion of the parameter year)

    For example:

    • Day 5 is valid in any month.
    • Day -5 is not valid in any month.
    • Day 35 is not valid in any month.
    • Day 31 is valid in some months but not others
    • Day 29 is valid in February only if it is a leap year

    You will need at least six test cases to ensure it works correctly.

  4. Write the function get_month_name(month). This function takes an integer parameter month and must return the name of the month as a string.

    For example, get_month_name(9) will return 'September'.

  5. Write the function is_valid_date(month, day, year) that takes integer parameters for the month, day, and year, and then returns True if this is a valid date and False if it is not a valid date.

    In this function, you will re-use the work from your previous (helper) functions. That is, you will call the helper functions, and test the results (using if/else statements).

    If the date is not valid, print an output message explaining why it is not valid.

    Finally, the function must return the result of True or False.

Testing Your Work

Here is some sample output that was generated by some of our test cases. Note that you will need to write the code to call your is_valid_date function – what is printed below is only the output and a print out of the return value (True or False).

This is not an exhaustive list of test cases. Moreover, the autograder script will test only some of your cases before the deadline (to ensure that your) functions are callable and return the correct type of data. They will not test your logic thoroughly until after the due date.

02/29/2016 is a valid date.
True

02/29/2017 is a not valid date, because day 29 is not valid date for February 2017
False

02/29/2018 is a not valid date, because day 29 is not valid date for February 2018
False

01/32/2018 is a not valid date, because day 32 is not valid date for January 2018
False

13/07/2018 is a not valid date, because 13 is not a valid month.
False

Important Notes

  • You do not need to match this output exactly. You do need to correctly return True or False.

  • Do not take user input from the keyboard (i.e., the input function) in any of your code for this problem.


Submitting Your Work

You should use Gradesope to submit the following files:


Warnings

  • Make sure to use these exact file names, or Gradescope will not accept your files. If Gradescope reports that a file does not have the correct name, you should rename the file using the name listed in the assignment page.

  • If you make any last-minute changes to one of your Python files (e.g., adding additional comments), you should run the file in Spyder after you make the changes to ensure that it still runs correctly. Even seemingly minor changes can cause your code to become unrunnable.

  • If you submit an unrunnable file, Gradescope will accept your file, but it will not be able to auto-grade it. If time permits, you are strongly encouraged to fix your file and resubmit. Otherwise, your code will fail most if not all of our tests.

Important note regarding test cases and Gradescope:

  • You must test each function after you write it. Here are two ways to do so:
    • Run your file after you finish a given function. Doing so will bring you to the Shell, where you can call the function using different inputs and check to see that you obtain the correct outputs.

Warning: Beware of Global print statements

  • The autograder script cannot handle print statements in the global scope, and their inclusion causes this error:

autograder_fail

*   Why does this happen? When the autograder imports your file, the `print` 
    statement(s) execute (at import time), which causes this error.

*   You can prevent this error by not having any `print` statements in the global scope.
    Instead, create an `if __name__ == '__main__':` section at the bottom of the file, and
    put any test cases/print statements in that controlled block. For example:

        if __name__ == '__main__':

            ## put test cases here:
            print('future_value(0.05, 2, 100)', future_value(0.05, 2, 100))

*   `print` statements inside of functions do not cause this problem.