ICS 32 Winter 2022
Exercise Set 1

Due date and time: Friday, January 14, 11:59pm


Getting started

First of all, be sure that you've read the Reinforcement Exercises page, which explains everything you'll need to know, generally, about how the reinforcement exercises will work this quarter. Make sure you read that page before you continue with this one.


Problem 1 (3 points)

Write a Python function called lines_of_code that takes a Path object as a parameter, which is expected to be the path to a Python script. It returns how many lines of Python code are contained within the script. Note that not all lines of text are considered to be "lines of code." We'll want to skip a few things, so we aren't counting lines that lack meaning; do not count lines that have these characteristics:

(Note that when I refer to "comments" here, I'm referring only to comments, i.e., the portions of a line that begin with a # character. Some people use string literals as comments — most notably, they use multiline string literals, because Python lacks multiline comments and a string literal "floating" in one's code turns out to be legal, though it's not a comment, because it still gets evaluated at run-time — but these aren't comments, so you would want to count them.)

The function returns an integer that indicates how many lines of code are in the Python script.

If the file cannot be opened successfully, or if the file contains something other than text, the function should raise an exception, though it's not important what kind of exception is raised — any exception will do, and it's fine if you raise different kinds of exceptions in different scenarios. However, the file is required to be closed in any case in which it had been opened.

What to submit

Submit one Python script named problem1.py, which contains your lines_of_code function. A docstring is not required, since we've all agreed already on what problem we're solving here.

The function and any additional functions you've written that it calls (if any) are all you need to write in the module; we don't need you to write automated tests, though you can, as long as importing the module can be done without generating any output or other effects, so that we can run automated tests of our own. (One way to meet that last requirement is for your automated tests to appear in a separate function, so they only run if you call it explicitly. If you're not sure how to meet this requirement, the safest thing is not to submit any automated tests; if we can't run our automated tests, you may not receive full credit here.)


Problem 2 (2 points)

When we learn a new technique, it can be tempting to want to find as many ways to apply that technique as possible, as this provides us the practice necessary to allow us to really internalize it. However, in our conversation about Recursion, one of the things we briefly discussed is that, even though it's a form of repetition (i.e., doing the same thing repeatedly), it's not a suitable solution to every problem that requires repetition; in other words, recursion isn't just a "better" or "fancier" loop. Where we draw the line between "repetitive problems that benefit from recursion" and "repetitive problems where recursion is not ideal" depends on the programming language in which we're writing, but since we're using Python in this course, we should understand where that line is best drawn in Python.

Previously, when we learned about loops, we used them to solve the problem of reading user input repeatedly until it was valid in some way. For example, we could write the following function to find the maximum value in a list of integers (or None if the given list is empty).

def find_max(nums: list[int]) -> int:
    '''
    Finds the maximum value in the given list of integers,
    or None if the list is empty
    '''
    largest = None

    for num in nums:
        if largest == None or num > largest:
            largest = num

    return largest
  1. Rewrite the find_max function so that it uses recursion to work its way through the list instead of using a loop. Your rewritten function should accept the same kinds of inputs (i.e., a one-dimensional list of integers) and generate the same outputs.
  2. In your view, is this a problem that's well-suited to a recursive solution in Python? Why or why not?

What to submit

Submit one PDF file named problem2.pdf, which shows your recursive solution to the find_max problem and, at least as importantly, your explanation of whether recursion was an appropriate solution to the problem and why.


Problem 3 (4 points)

One of the key learning objectives this week is Recursion, so let's make sure that we know how to apply it to a couple of problems.

  1. Write a Python function first_chars that takes one parameter, which is a nested list of strings. It returns a string containing the first character of all of the non-empty strings (at whatever depth they occur in the nested list) concatenated together. Here are a couple of examples of how the function should behave when you're done:
    >>> first_chars(['Boo', 'is', 'happy', 'today'])
    'Biht'
    >>> first_chars(['boo', 'was', ['sleeping', 'deeply'], 'that', 'night', ['as', ['usual']]])
    'bwsdtnau'
    
  2. Write a Python function count_bigger that takes two parameters, a nested list of objects and a threshold number. It returns an integer specifying how many of the objects anywhere in the nested list are numbers that are larger than the threshold. (For our purposes, "numbers" are either integers or floats.) There may be objects in the list other than numbers, in which case you would simply ignore them. Here are a couple of examples of how the function should behave when you're done:
    >>> count_bigger([1, 2, 3, 4, 5, 6], 2.5)
    4
    >>> count_bigger(['boo', [3.0, 'is', 'perfect'], 'today', 5], 3)
    1
    

Your functions should be using recursion where it's an applicable strategy. This doesn't mean that you're not permitted to use loops at all, but that you should be applying recursion in places where the solution needs it (i.e., where you would have to be doing things manually that recursion might otherwise be doing for you automatically).

Testing your work

The function and any additional functions you've written that it calls (if any) are the most basic requirement to meet here. However, you are also required to write assert-statement-based tests, as described in the Testing lecture, to determine that your functions are working correctly in various cases. Your tests shouldn't generate any output or other effects, because we'll be running automated tests of our own separately; if we can't run our automated tests, you may not receive full credit here.

What to submit

Submit one Python script named problem3.py, which contains the two functions described above, along with your assert-statement-based tests. Docstrings are not required, since we've all agreed already on what problem we're solving here.


Problem 4 (1 point)

We've begun to see a technique appearing in Python programs written in lecture and the notes: the use of an if __name__ == '__main__': statement, which acts as a sort of "starting point" for our programs, in the same vein as the main method in a Java program, the main function in a C++ program, and so on. When you're learning a new programming language, it's important to recognize the ways in which the techniques you're learning are similar to what you've seen, but also to recognize the ways in which they're different.

Answer the following three questions about the if __name__ == '__main__': technique.

  1. Under what circumstances is the __name__ == '__main__' expression considered truthy? Under what circumstances is it considered falsy?
  2. When we write some of our code in the body of an if __name__ == '__main__': statement, what problem are we trying to solve? (In other words, why not just put that same code into the script without putting it into an if statement?)
  3. Suppose that we assign to a variable within the if __name__ == '__main__': statement. What is that variable's scope? How long does it live? Does this impact your choice about whether you should assign to variables in an if __name__ == '__main__': statement in a Python script?

What to submit

Submit one PDF file named problem4.pdf, which contains your answers to the three questions above.


Deliverables

Gathering your files for submission

In Canvas, you'll find a separate submission area for each problem. Submit your solution to each problem into the appropriate submission area. Be sure that you're submitting the correct file into the correct area (i.e., submitting your Problem 1 solution to the area for Problem 1, and so on). Under no circumstances will we offer credit for files submitted in the incorrect area.

Submit each file as-is, without putting it into a Zip file or arranging it in any other way. If we asked for a PDF, for example, all we want is a PDF; no more, no less. If you submit something other than what we asked for, we will not be offering you any credit on the submission. There are no exceptions to this rule.

Of course, you should also be aware that you're responsible for submitting precisely the version of your work that you want graded. We won't regrade an exercise simply because you submitted the wrong version accidentally, and we won't be able to offer any credit on exercises that you forgot to submit.

Can I submit after the deadline?

Unlike some of the projects in this course, the reinforcement exercises cannot be submitted after the deadline; there is no late policy for these. Each is worth only 3% of your grade, so it's not a disaster if you miss one of them along the way.