ICS H32 Fall 2025
Exercise Set 3 Solutions
What's here?
These are solutions to a set of exercises, along with a fair amount of additional background explanation. Note that I've gone into more detail with those explanations than we would have wanted or expected students to do, but I'm using these as an opportunity to solidify your understanding; don't take this as an indication that you should be writing your answers in as much detail as I am here.
Problem 1
While the usefulness of namedtuples and dictionaries intersect somewhat, they each excel at slightly different things.
Person or Point, rather than a non-specific type, such as namedtuple, which means it's possible at run time (and in your mind while you write a program) to distinguish between different kinds of namedtuples.Point, it's worth allowing our program to fail fast when we try to create a Point that has those values missing or has values that shouldn't be there.p.x, where the field's name is x). This means that we need to know those names at the time we're designing our program, whereas we can decide at run time what a dictionary's keys should be.int, can be used instead.Perhaps this is a good way to sum up this issue: While you could use a dictionary for anything for which a namedtuple could be used, you're better off using the right tool for the right job. Many times, the right tool for a job is the one that not only does all that you need, but also does only what you need.
Problem 2
The testing requirement imposes what may have seemed like a burden, but is actually a long-term benefit; you had to write this function in a way that it was testable. Unfortunately, there's not a straightforward way to use assert statements to test a function that prints to the Python shell, so it became necessary to divide this problem into its component parts. Paradoxically, the more carefully you did that job, the easier it was to test those parts. It may have seemed uncomfortable to be thinking about your solution in that way, if you're not accustomed to doing it, but the ability to write many small functions instead of fewer larger ones is the key that unlocks your ability to write substantially larger programs than the ones we've been assigning.
There are probably a lot of good ways to break this up, but this is the most carefully divided version I came up with. If you're wondering "How did you end up here?", the answer is "incrementally." I started solving the problem, calling functions that didn't exist yet when I recognized a new and separate problem to solve, then started working on that new and separate problem, continuing until I had no problems left to solve.
There are a couple of things I should point out about this solution.
pretty_print function. If I later discovered a need for some of those protected functions to be public, that's a decision I can change later without breaking anything; it's making public things protected (after other parts of your program are already reliant on them) that causes problems!print function.
Problem 3
As with my solution to Problem 2, the way I approached this problem was incremental, gradually adjusting what functions I had written until I had virtually everything in a form where it could be tested. The way I started my solution is not what it looked like when I finished, and that's normal; the sooner you let go of the idea that you'll always know exactly what to write and always make precisely the right design decisions every time, the sooner you'll be able to write larger programs without feeling overwhelmed. If we're careful about how we design it, software is quite malleable; where it begins to harden is where we haven't tamed its complexity.
There are a couple of things I should point out about this solution.
build_grade_report function. If I later discovered a need for some of those protected functions to be public, that's a decision I can change later without breaking anything; it's making public things protected (after other parts of your program are already reliant on them) that causes problems!_parse_student and built a dictionary out of them. (I'd have included the building of the dictionary, too, but the best way to approach that was to use Python features we've not yet learned.)Problem 4
Problem 5
The _read_line, _expect_line, and _write_line functions are all tools that are written specifically to make it easier to write the polling module itself. In other words, they aren't the tools that the module is meant to provide to code in other modules; they're tools that make it easier to build those bigger tools.
In particular, the entire goal of the polling module is to hide the details of the implementation of the Polling protocol. While the public functions mirror the interactions that are part of the protocol, none of the details of how those interactions work, behind the scenes, are noticeable to code in modules outside of polling.py. All the rest of the program needs to know are things like "Given a host and a port, we can connect and get back an object representing that connection, which, if we pass it to other public functions, will let us ask the server questions" and "If we ask the server for the questions, we'll get back a list of them," without needing to rely on any of the details of how any of that stuff works. If I completely redesigned the details of the Polling protocol (i.e., what gets sent back and forth between the client and server), but maintained the same basic interactions (i.e., there's still a way to ask the server the same questions we could before), then the functions in polling.py would need to be rewritten, but none of the rest of the program would need to be touched.
That's why _read_line, _expect_line, and _write_line are clearly on the "protected" side of the line, ultimately; they're functions that implement the details of sending and receiving lines of text, which are a behind-the-scenes detail of how the protocol works, which is exactly what the polling module is intended to hide from the rest of the program.
(If the word "hide" seems strange there, you'll likely see the phrase "information hiding" again in your travels in computing, as it's one of the central tenets of software engineering. In the context of a large and complex program, the idea that parts of the program hide details from other parts is a good thing; a hidden detail is one that no other part of the program can depend on, which means that it can be changed without affecting any other part of the program.)