ICS 32A Fall 2023
Exercise Set 3

Due date and time: Friday, October 27, 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 (1 point)

Python offers two seemingly similar features: namedtuples and dictionaries. Both offer the ability to associate values with corresponding names, so that those values can be looked up again by those same names. So, it stands to reason that anything you could store in a namedtuple could also be stored in a dictionary instead.

If that's true, then it's worth considering why Python offers both. Think about that and then answer these two questions about them.

  1. What is an advantage of namedtuples over dictionaries?
  2. What is an advantage of dictionaries over namedtuples?

You won't need to write a page to answer this question, but you will want to briefly explain why, in a sentence or two, your proposed advantage is actually an advantage.

What to submit

Submit one PDF file named problem1.pdf, which contains your answer to these two questions.


Problem 2 (3 points)

Write a function pretty_print, which takes one parameter that can be any type of namedtuple. It "pretty prints" the contents of the namedtuple, including both the names of its fields and their values. This is subject to a few rules.

A couple of examples follow.


>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y', 'z'])
>>> pt = Point(z = 5, x = 9, y = 13)
>>> pretty_print(pt)
    x: 9
    y: 13
    z: 5
>>> Person = namedtuple('Person', ['name', 'age', 'favorite'])
>>> instructor = Person(name = 'Alex', age = 47, favorite = 'Boo')
>>> pretty_print(instructor)
        name: Alex
         age: 47
    favorite: Boo

Testing your work

You are required to test as much of this work as you can, which means you would want to write assert-statement-based tests, as described in the Testing notes. However, your tests should not generate any output or other effects, which means you won't be able to test precisely what gets printed by the print function — we haven't learned techniques that would allow us to test that, anyway! — so your best bet is to write helper functions that are "pure" (i.e., whose outputs are determined solely by their inputs), with as much of the functionality appearing in those helper functions as possible. Everything but the printing should be tested.

Your tests should not generate any output or other effects, however. 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 module named problem2.py, which contains your pretty_print function, along with your helper functions and any assert-statement-based tests.

Docstrings and type annotations are required on your helper functions, because we haven't agreed ahead of time on what your design should look like; by documenting that design, you'll also have thought carefully about what problem each function solves, whether they'll fit together properly, and so on. That's the funny thing about a documentation requirement: It's not entirely about documentation, though that's an ancillary benefit; it's also about slowing down enough to think about one's own design before fully committing to it. Testing, as we'll see in more detail later this quarter (but you'll start to see it here, if you approach this in the way we're asking you to) has similar benefits.


Problem 3 (3 points)

In this problem, we'll write a Python module that defines two things publicly.

The inputs

The first parameter to your function is the path to a file containing score information, but the interesting thing about it is the format of that information, which we'll need to agree on. What you'll expect is a text file in which each line of text represents one student's information. The first thing you'll see on each line is the student's UCInetID (i.e., an identifier that's unique for each student); after that will be a sequence of numbers that are separated by at least one space, which are that student's raw scores. Any line of text consisting only of spaces, or any line whose first non-space character is a # is to be ignored. Note that it is possible for a student to have no scores.

The second parameter to your function is a dictionary where the keys are letter grades and the values are tuples specifying the range of total scores that would lead to that letter grade. A tuple containing only one value would mean "The given score or anything higher," while a tuple containing two values would mean "Any score that's greater than or equal to the first of these, but is less than the second of these." For example, if the dictionary looked like this:


{'A': (600, ), 'B': (500, 600), 'C': (400, 500), 'D': (300, 400), 'F': (0, 300)}

then we'd expect any student scoring at least 600 points total would receive a grade of A, any student scoring at least 500 points but less than 600 would receive a B, and so on. Don't assume that the letter grades will always be A, B, C, D, and F, or even that they'll always be a single letter. It is expected that the ranges of scores will not overlap; if they do, your function can output any grade that matches (i.e., if 17 points is either a B or a C, you can return either grade for a student in that case).

The output

Your function will return a dictionary where the keys are the UCInetIDs of students who are listed in the file, and where the corresponding values are Student namedtuples with scores being a list of the student's scores (in the order listed on the corresponding line of the file) and grade being the student's grade.

Note that grades are calculated by determining the sum of all of a student's raw scores (i.e., there is no weighting scheme that makes one assignment worth more than another, which is different from the actual grading formula used in this course) and comparing it to the given grade ranges. If the total score is not in any of the given grade ranges, the student's grade should be specified as the Python value None.

If the file cannot be opened or it cannot be read to completion, the function should raise an exception; it's not important what kind of exception it is, but the file should be closed in any circumstance in which it was opened successfully.

An example

Suppose that you had a file called scores.txt in the same directory as your problem3.py file, in which the following text appeared.


# Alex has work to do, but is improving
thornton 30 40 50 60 70 80 90
# Boo is perfect, as usual
boo 100 100 100 100 100 100 100
# Student that submitted no work; total score is 0
didnothing

Here's how your function should behave, given that file.


>>> grade_ranges = {'A': (600, ), 'B': (500, 600), 'C': (400, 500), 'D': (300, 400), 'F': (0, 300)}
>>> build_grade_report(Path('scores.txt'), grade_ranges)
    {'thornton': Student(scores=[30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0], grade='C'),
     'boo': Student(scores=[100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0], grade='A'),
     'didnothing': Student(scores=[], grade='F')}

Testing your work

As with Problem 2, you are required to test as much of this work as you can, which means you would want to write assert-statement-based tests, as described in the Testing notes. Your tests need to be self-contained, which means they should not generate any output or other effects, and they should not depend on the existence of files in the same directory as your problem3.py file. This means it won't be possible to test everything, but, to the extent that you divide your function up into smaller ones (as you did in Problem 2), you'll find that you can test everything except opening the file and reading lines of text from it.

What to submit

Submit one Python module named problem3.py, which contains your build_grade_report report, the Student namedtuple, along with your helper functions and any assert-statement-based tests.

Docstrings and type annotations are required on your helper functions, because we haven't agreed ahead of time on what your design should look like, for all the same reasons described in Problem 2.


Problem 4 (2 points)

While there are many protocols in use on the Internet, and while they certainly differ in their details, there are some aspects of those protocols that recur. It's those recurring details that make us wonder whether there's a deeper principle at work; when lots of smart, experienced people solve a problem the same way, it's often because they've internalized a key insight that makes that solution the obvious one.

We've seen a number of protocols already this quarter: the "echo" protocols from the Networks and Sockets lecture, the Polling protocol from the Protocols lecture, the Shakespeare protocol from the previous exercise set, and the I32CFSP protocol you'll be implementing in Project 2. While they, too, differ in their details, they do have one common detail that they share: The client speaks first. And, other than the "echo" protocols, they share one other common detail: When the client speaks first, what's sent is a message that identifies, in some way, what protocol the client intends to use. These turn out to be commonalities not only amongst the protocols in ICS 32A, but also amongst Internet protocols more generally, so one might wonder why that is. Rather than wonder about it, let's think about it and answer a couple of questions about it.

  1. Why is it so common in Internet protocols that clients speak before servers do? (In other words, what would be an important advantage of having a client speak first, or, thought differently, an important disadvantage of having a server speak first instead?)
  2. Why is it so common for clients to begin with a "hello" message that, in some way, identifies what protocol it wants to use? Why not just get down to business and start by asking the server, more directly, for the first thing the client wants?

What to submit

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


Problem 5 (1 point)

In the Protocols lecture, we mainly wrote a medium-sized program that implemented a Polling client (i.e., a program that can interact with the Polling server, so that a user can see the questions, vote on them, and see the results without knowing the details of how the Polling protocol works).

In polling.py, three of the functions — _read_line, _expect_line, and _write_line — were marked as protected, while the others were not. In a sentence or two, what would be a good rationale for making these protected? In other words, why would I have written them that way, instead of making them public instead? (Note that I'm asking specifically about these functions, not for a general definition of what public or protected functions are. This isn't about the concept; it's about the application of the concept.)

What to submit

Submit one PDF file named problem5.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.