ICS 32A Fall 2023
Exercise Set 6

Due date and time: Monday, November 20, 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 (2 points)

Recently, one of the things we talked about in detail is the mutability of objects. It's not uncommon to be introduced to the concept by noting that some of the types built into Python, such as strings and tuples, have objects that are immutable, while others, like lists and dictionaries, have objects that are mutable. And it's certainly true that we have to accept the design decisions made by others; if we use someone else's tools, then we need to be able to make use of them the way they're meant to work.

However, there's a flip side to this issue: When we're the ones making the design choices, mutability becomes a question for us to answer. If we design a class, should its objects be mutable or immutable? If we write a function, should it mutate its parameters? If we aren't equipped to think about these things yet, then we might make decisions whose impacts we don't fully understand. But now that we're more keenly aware that there's a distinction between mutable and immutable objects, we need to make sure we get a clear picture of the implications of that difference.

To do that, let's consider the following somewhat skeletal Python class that partially solves the problem of representing the information we might like to know about a university course — maybe in a system like Canvas.


class Course:
    def __init__(self, students: list[Student]):
        self._students = students

    def students(self) -> list[Student]:
        return self._students

    def add_student(self, student: Student) -> None:
        self._students.append(student)

In particular, we say that a course is a collection of students, that we can find out what students are enrolled in that course, and that we can add a new student to the course, but that there is no way for a student to drop. (Of course, there are plenty of other things that we might like to be able to do, but let's stick with these few features for now.)

Now let's consider the impact of mutability on this design.

  1. Are objects of our Course class mutable or immutable? In a sentence or so, briefly explain why.
  2. The __init__ method accepts a parameter that is a list of Student objects, which we store directly in an attribute within self. Given that lists are mutable, does this pose any design risks (i.e., ways for our program to behave unpredictably even if no code in the Course class is incorrect)? If so, are there ways to mitigate those risks?
  3. The students method returns the list of Student objects stored within a Course. Given that lists are mutable, does this pose any design risks (i.e., ways for our program to behave unpredictably even if no code in the Course class is incorrect)? If so, are there ways to mitigate those risks?

What to submit

Submit one PDF file named problem1.pdf, which contains your answers to these three questions.


Problem 2 (3 points)

One of the things we explored recently was algorithms for manipulating Two-Dimensional Lists. Since this is something you're going to need to do in a future project, it's best if you take some time now to make sure you've had some practice with these concepts. So, let's write a couple of Python functions that deal with two-dimensional lists.

Here's one quick example of each function in action:


>>> a = [['x', 'y', 'z'], ['w', 'v', 't'], ['p', 'r', 's']]
>>> b = reverse_transpose(a)
>>> a
    [['x', 'y', 'z'], ['w', 'v', 't'], ['p', 'r', 's']]
>>> b
    [['s', 't', 'z'], ['r', 'v', 'y'], ['p', 'w', 'x']]
>>> c = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> calculate_sums(c)
>>> c
    [[45, 33, 18], [39, 28, 15], [24, 17, 9]]

Docstrings are not necessary, since we've already agreed on what problems are being solved here.

Testing your functions

As usual, you'll need to write automated tests, but since we've learned some new techniques for that this week, let's put them to use. Using the unittest module from Python's standard library, as we did in the Test-Driven Development lecture, write a unit test class for each of your functions. While we don't have a specific requirement with regard to how many tests you write — as the goal when writing tests is a qualitative, rather than quantitative, one — you'll want to be sure that you've separated different kinds of test cases into separate methods in your unit test classes, and that you've thought carefully about what tests would be interesting to include, rather than just writing as many tests as you can without considering whether they're meaningfully different from one another.

What to submit

Submit two Python scripts, one named problem2.py and the other named problem2tests.py. The first of these files should contain your functions, while the second of these files should contain your unittest-based unit tests.


Problem 3 (3 points)

Below are links to two files that are necessary to complete this problem. Download them and take a look through them; there's not much code there, but you'll want to start by reading and understanding it.

What you'll see in problem3.py are two classes called Student and Club. Students have student IDs; clubs are collections of students, in which the students are arranged by those same student IDs. Meanwhile, what you'll see in problem3tests.py are unit tests written using Python's unittest module. In general, these two files were developed using the Test-Driven Development style that we talked about in lecture.

Your job in this problem is to continue where I left off and work through three more iterations using test-driven development. Challenge yourself to use the test-driven development style, even if it feels unnatural to you, because part of the goal here is that you've experienced this process, as it's one that's used in some (but most definitely not all) real-world software development; this is a road worth going down, at least sometimes, so that you'll know how to do it when the time comes.

We've chosen the three features you'll work on, but you'll make the decisions about what tests you'll need to write, what changes you'll need to make, and what refactorings will be appropriate at the end of each iteration. Here are the features you'll work on. (Work on them in this order.)

  1. Given a student ID, ask a club for the student who has that ID, if any. (What happens when the student is not in the club? You'll need to decide.)
  2. Since we're arranging all of the students by their IDs, it should not be possible for two students to have the same ID. For that reason, ensure that attempting to add a student to the club when another student is already in the club should be an error.
  3. Each student should have a name, in addition to just a student ID.

Don't fall into the trap of worrying solely about how we're going to grade this work. We're not looking for a particular set of tests or an exact set of modifications, and there's not only one way to do all of this. We're looking for evidence that you went through the process and implemented the three new features, while leaving the remaining features in place.

What to submit

Submit the two Python scripts, one named problem3.py and the other named problem3tests.py, which contain your modifications after you've finished all three iterations. There's no need (or mechanism) to submit the intermediate results (e.g., your code after the first iteration); we just want to see where you ended up.


Problem 4 (2 points)

When presented with the instructions about creating virtual environments in the context of our discussion of Third-Party Libraries, I've found that many students over the years have ignored them altogether, or have asked me, in so many words, "Why do I have to do this?" And, of course, I can understand that impulse; when working amongst a lot of complexity, it's healthy to want to skip some of it, thinking instead "I'll figure this out later."

Yet, despite its requirement that you wade into the waters of the command line and issue commands like python -m venv . or pip3 install pygame, I'm asking you to do it, but it's not a gratuitous thing. It solves a problem, albeit one that you might not notice that you have right away. (That's the thing about tools like these; it's not always clear initially why some of their complexity is there, which is why it's important to stop and think about it early.)

Let's assume that you never use a virtual environment. What problem might you be susceptible to, which wouldn't have affected you if you had used virtual environments instead? Two or three sentences is enough here, and if you think there are multiple problems that virtual environments solve, you don't need to list all of them; describing one of them in a little bit of depth is better than scratching the surface of more than one of them.

What to submit

Submit one PDF file named problem4.pdf, which contains your answer to this question.


Deliverables

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 (e.g., a text file when we asked for a PDF, even if its filename ends in .pdf), 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.

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, with the lowest score dropped — see the Reinforcement Exercises page for details — so it's not a disaster if you miss one of them along the way.

You're responsible for making a submission in order to receive credit, which means you'll want to be sure that you've remembered to submit your work and verify in Canvas that it's been received. A later claim of having forgotten to submit your work or having misremembered the due date will not be grounds for a resubmission under any circumstances.

What do I do if Canvas adjusts my filename?

Canvas will sometimes modify your filenames when you submit them (e.g., by adding a numbering scheme like -1 or a long sequence of hexadecimal digits to its name). In general, this is fine; as long as the file you submitted has the correct name prior to submission, we'll be able to obtain it with that same name, even if Canvas adjusts it.