ICS 32 Winter 2022
Exercise Set 5

Due date and time: Friday, February 11, 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)

This week, 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, 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 (4 points)

One of the things we explored this week 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 (2 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 people first learn about the Test-Driven Development process — especially if they've already internalized another way to write programs — it's not uncommon for them to find it to be a big departure from what they've seen in the past; as a result, it's also not uncommon for people to react negatively toward it. While TDD certainly isn't perfect, it's got some advantages, but if all you can see is how it's different from what you're used to, it's hard to see those advantages. So let's take a little time to think about why TDD is structured the way it is, by answering a few questions about it.

  1. TDD asks you to write tests before you write the code you're planning to write. What is an advantage of writing the tests first?
  2. TDD asks you to write code that will sometimes turn out to be incorrect in the long term, but that will cause a test to pass in the nearer term. Why is it an advantage to write code this way, as opposed to striving for a complete and correct solution to begin with?
  3. Why do the tests written in each iteration make it easier to apply the technique of refactoring?

(Note that it might be better for you to answer this question after you've had a chance to do Problem 3. Having experienced the test-driven development process will likely give you some insight that you'll probably lack if you've never tried it.)

What to submit

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


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, 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.