Class 10 - Tests for tables & privacy analysis
Date: September 30, 2026
For all work with tables, refer to the Tables page in the menu at the top of the page!
Skills: 0, 1, 3, 10
Notes (review before class)
Examples for Table-Producing Functions
To test a function that produces a table (annotated Table), compare its result
with the table you expect. For give_raises, from Class 9:
-
Simplify the input table: a small one, with just the columns the function uses, and enough variety in them:
wages_test = table(["hourly-wage"], [[15], [20], [18], [18]]) -
Write computations in the expected table, rather than doing them by hand. The expected table goes right in the test, without a name (the input table can be made in the test, too):
def test_give_raises():
assert give_raises(wages_test) == table(
["hourly-wage"], [[15 * 1.1], [20], [18 * 1.1], [18 * 1.1]]
) -
Build the expected table from rows of an existing one:
t.columns()is the list oft's column names, andtableacceptsdictrows, like those fromrow. Filteringemployeesto those working exactly 40 hours should give its first and fourth rows:emps_at_40 = table(employees.columns(), [employees.row(0), employees.row(3)])
lambda: Functions without Names
Why not skip a helper like below_1k (Class 8)?
shuttle.filter(r["riders"] < 1000)
- This is a
NameError:risn't defined. Arguments are evaluated before a function (or method) runs, sor["riders"] < 1000is evaluated beforefilterstarts. Inbelow_1k,ris a parameter, so the body waits untilrhas a value. lambdamakes a temporary function, without a name:lambda r:means "a function that takesr", and the expression after the colon is what it returns:
shuttle.filter(lambda r: r["riders"] < 1000)
- Same result as
shuttle.filter(below_1k):filtercalls whatever function it's given through its own parameter (say,keep(row)). Onlybelow_1kgets an entry in the directory. - A
lambda's body is one expression: noreturn, type annotations, orif. Use it for simple functions given tofilter,transform_column, oradd_column; write anything more as a named function, with the full design process. (Many, though not all, languages havelambda.) - Try: add 10 riders to each row of
shuttle, with alambda.
Charts
Table methods also make charts (Tables lists them all).
freq_bar_chart draws a bar for each different value in a column, as tall
as the number of rows with that value:
employees.freq_bar_chart("hourly-wage") # bars for 15, 18 (twice as tall), and 20
Announcement -- tests for your tests in autograder (10 mins)
-
Start with HW3, our autograder runs against your tests, not just your code.

-
"Your tests against our correct implementation(s)" means we take your tests and make sure they pass on an implementation that works. For example, consider the
nm_squarefunction that is an example for Skill 1. If you wrote:def nm_square(n: float) -> float:
"""squares"""
return n * n
def test_nm_square():
assert nm_square(1) == 1Then we might run your tests (just one!) against a correct implementation:
def nm_square(n: float) -> float:
"""a correct square"""
return n ** 2And in this case, all your tests pass, so you would get credit. If you had mis-interpreted the question as
square rootand had done:import math
def nm_square(n: float) -> float:
"""squares"""
return math.sqrt(n)
def test_nm_square():
assert nm_square(1) == 1
assert nm_square(4) == 2Then you would not pass this, since when we combine our implementation with your tests, we get:
def nm_square(n: float) -> float:
"""a correct square"""
return n ** 2
def test_nm_square():
assert nm_square(1) == 1
assert nm_square(4) == 2And the second test fails.
-
"Your tests against our incorrect implementation(s)" means we take your tests and run them on implementations that have bugs we know! We put them in! And the goal is that at least one of your tests fails. This means your tests are thorough enough to cover the buggy behavior in our implementation. If you had:
def nm_square(n: float) -> float:
"""squares"""
return n * n
def test_nm_square():
assert nm_square(1) == 1We might have an incorrect (i.e., buggy) implementation as:
def nm_square(n: float) -> float:
"""an incorrect square -- just returns input"""
return nBut even though this definitely isn't square, when we combine your tests with our implementation:
def nm_square(n: float) -> float:
"""an incorrect square -- just returns input"""
return n
def test_nm_square():
assert nm_square(1) == 1All your tests (just one, here) pass! Which is not good. The reason they pass is that your tests were not good enough to distinguish between a function that just returned the input, doing nothing to it, and actually squaring. This test you have isn't wrong (so you shouldn't remove it!) -- it doesn't fail on the correct implementation -- but you should add more tests, to ensure you cover more behavior. Adding just one more test, to make your submission look like:
def nm_square(n: float) -> float:
"""squares"""
return n * n
def test_nm_square():
assert nm_square(1) == 1
assert nm_square(2) == 4Is enough for you to catch our bug, since when we combine all your test with out buggy implementation, one of your tests now fails!
def nm_square(n: float) -> float:
"""an incorrect square -- just returns input"""
return n
def test_nm_square():
assert nm_square(1) == 1
assert nm_square(2) == 4- As you see in the screenshot above, we may (and often do) have multiple incorrect implementations, that have different bugs. So if you catch some, but not all, think about what cases your tests aren't covering (for example, above I don't have any inputs that are negative. And I don't have zero!).
Recap (15 mins)
A store takes 20% off every price under 100. Read through the program:
prices = table(
["price"],
[[50], [120], [80], [40], [50], [80], [80]],
)
def discount_price(price: float) -> float:
"""reduces price by 20%, if it is below 100"""
if price < 100:
return price * 0.8
else:
return price
def test_discount_price():
assert discount_price(50) == 50 * 0.8
assert discount_price(120) == 120
def apply_discounts(t: Table) -> Table:
"""transforms 'price' column by reducing 20%, if value is below 100"""
return t.transform_column("price", discount_price)
def test_apply_discounts():
test_table = table(["price"], [[50], [120], [80], [40]])
assert apply_discounts(test_table) == table(
["price"], [[50 * 0.8], [120], [80 * 0.8], [40 * 0.8]]
)
def discounted_rows(t: Table) -> Table:
"""the rows of t whose 'price' gets the discount"""
return t.filter(lambda r: r["price"] < 100)
def test_discounted_rows():
test_table = table(["price"], [[50], [120], [80]])
assert discounted_rows(test_table) == table(["price"], [[50], [80]])
apply_discounts(prices).freq_bar_chart("price")
- Why is
discount_pricea named function, whilediscounted_rowsuses alambda? - In
test_apply_discounts, why write80 * 0.8rather than64.0? Which row of itstest_tablechecks theelseofdiscount_price? - How many bars does the chart have, and which value's bar is tallest?
The class exercise also includes a privacy analysis, using the method from the Class 6 Reading, which you've seen in labs but haven't yet done as an exercise in class.
Class Exercise (30 mins)
- Design a function that takes a table that has a "price" column and adds a new column "tax", which is the sales tax amount (look up the sales tax where you are; to calculate the sales tax rate, multiply that by the price. If you live somewhere with no sales tax, you can use 5%). You can assume there is not already a tax column.
- Create a function to carry out the "obfuscation" exercise from last class, and
write tests for it. Note, your test tables should only need an
"item"column! - Consider our privacy analysis, which is based on answering five questions:
Question Answer What type of information is shared? Who is the subject of the information? Who is the sender of the information? Who are the potential recipients of the information? What principles govern the collection and transmission of this information? - Apply that analysis to the Shamazon resume tool that we covered in Class 6.
Wrap-up (5 mins)
lambdaallows us to more concisely use operations that expect functions as inputs, liketransform_column,add_column, orfilter, when the function is simple.- Table functions should follow the same design process, and include tests, just like other functions!