Overview
A bookstore keeps one table of orders per month. This assignment has you practice working with and combining list operators to answer questions about the orders, both within and across months.
Learning objectives
After this assignment, you should be able to:
- Combine several list operations to answer a question about data
- Extract lists from tables and compute on them
- Read list code and explain what it computes
- Write properties that check the output of a function
AI and collaboration policy for this assignment
Write the code, the answers, and the properties yourself, without AI. You may use AI to copy checks to your hw4-tests file, as described at the end of the document.
Setup
- Make a copy of
hw4-code.arr.
You may use filter, map, distinct, remove, member, all, any, append, and length, plus get-column and all-rows to get lists out of tables. Post to Ed if you think you need something else.
Part 1The Data
An order table has three columns: the customer, the genre of book, and the price. Each customer may appear many times. The customer "test" is an internal account used to check that the website works.
customer | genre | price
---------------------------
ana | fiction | 12
ben | mystery | 25
ana | fiction | 18
...
The starter file gives you two kinds of tables:
sample1,sample2, andsample3are small tables (10, 8, and 4 orders). The examples in this handout use them, so you can check your work by hand.januarythroughjuneare the real data: one table per month, with 100 to 200 orders each, loaded from a Google sheet. You can't check these by hand. Part 4 shows how to test on them.
Part 2Writing List Programs
Testing Expectations: For each of these functions, you should have a check block with a strong set of tests; these will be used to try to detect buggy programs in the autograder. To simplify the autograding process, it's okay for your tests to all be in check blocks instead of where blocks.
Write num-customers(orders :: Table) -> Number, the number of different customers in the table. For sample1 it is 5; for sample2, 7.
Write num-real-orders(orders :: Table) -> Number, the number of orders not placed by the "test" account. For sample1 it is 9; for sample2, 7.
Write num-genres-both(m :: Table, a :: Table) -> Number, the number of different genres sold across the two tables together. For sample1 and sample2 it is 5.
Write big-order-customers(orders :: Table, cutoff :: Number) -> List<String>, the customers, with no repeats, who placed at least one order of price cutoff or more. For sample1 and a cutoff of 20, the answer is ben, cy, and dee.
Write loyal-customers(months :: List<Table>) -> List<String>, the customers, with no repeats, who placed at least one order in every table in the list. You may assume the list has at least one table; months.first is its first element. For [list: sample1, sample2] the answer is ana, ben, cy, dee, and test. For [list: sample1, sample2, sample3] it is ana, dee, and test (ben and cy missed sample3).
Write new-genres(earlier :: Table, later :: Table) -> List<String>, the genres, with no repeats, sold in later but not in earlier. For sample1 then sample2, the answer is cooking and poetry. For sample2 then sample1, it is empty.
Order of results: for tasks Task 4, Task 5, and Task 6, the order of the names in your list does not matter (the autograder ignores it). For your own tests on the small tables, use examples whose answer has at most one element, or the checkers from Part 4.
Part 3Reading List Programs
Here are three functions with unhelpful names. They are in the starter file so you can run them.
fun mystery1(orders :: Table) -> List<String>:
rows = orders.all-rows()
names = distinct(map(lam(r): r["customer"] end, rows))
filter(lam(c): filter(lam(r): r["customer"] == c end, rows).length() >= 3 end, names)
end
fun mystery2(a :: Table, b :: Table) -> Boolean:
later = b.get-column("customer")
all(lam(c): any(lam(x): x == c end, later) end, a.get-column("customer"))
end
fun mystery3(orders :: Table) -> Number:
rows = orders.all-rows()
genres = distinct(map(lam(r): r["genre"] end, rows))
filter(lam(g): filter(lam(r): r["genre"] == g end, rows).length() >= 3 end, genres).length()
end
For each function, add a doc string to the starter file that explains what the function computes (in terms of orders, customers, and genres). You can use the sample tables to help you figure out what each function does.
Part 4Property Tests
Nobody wants to compute the answers for a 150-row table by hand. Instead of writing down exact outputs, we can check relationships between a function's input and its output. This is a first taste of the idea; a few properties will not catch every bug.
For example, in lecture we saw an example of doubling all items in a list. No matter what input list you provide, if you provided a list of integers as input, every item in the output would be an even number. We could test this property with the following code:
fun is-even(n :: Number) -> Boolean:
num-modulo(n, 2) == 0
end
check:
nums = ... # some list, perhaps extracted from a table
all(is-even, double(nums))
end
In this assignment, we are giving you a set of properties (like is-even), asking you to write functions for them, then asking you to include check blocks that use those functions to test the outputs of some of our programs, as shown in the example above. We'll be doing more with testing via properties in the coming weeks. For now, we're just getting warmed up with the idea.
Write a checker for each of Task 4, Task 5, and Task 6. Each returns true when the output is acceptable for the input. The headers are in the starter file:
fun big-order-ok(orders :: Table, cutoff :: Number, out :: List<String>) -> Boolean:
doc: ```every name in out has an order of at least cutoff, names are
real customers, and no name appears twice```
...
end
fun loyal-ok(months :: List<Table>, out :: List<String>) -> Boolean:
doc: ```every name in out has an order in every table of months, and no
name appears twice```
...
end
fun new-genres-ok(earlier :: Table, later :: Table, out :: List<String>) -> Boolean:
doc: ```every genre in out is sold in later, none is sold in earlier,
and no genre appears twice```
...
endHint: Each checker should be written with a different approach than the function it checks. In particular, don't call the function you are checking.
Use a checker in a test like this:
check:
big-order-ok(january, 50, big-order-customers(january, 50)) is true
end
Write check blocks that use your checkers on the monthly tables:
big-order-customerson every month, with several cutoffs (including one that no order reaches).loyal-customerson several lists of months, such as the first one, two, three, ... six months.new-genreson at least four pairs of months, in both orders (for examplejanuarythenjune, andjunethenjanuary).
Some functions have properties beyond what the checkers say. Write check blocks, run on the monthly tables (every month, and every pair of months where a pair is needed), that confirm:
num-customersandnum-real-ordersnever return more than the number of rows in the table.num-genres-bothgives the same answer for(a, b)and(b, a).- Adding a table to the list can only shrink the result of
loyal-customers: every customer it returns for[list: january, february, march]is also returned for[list: january, february]. num-genres-both(a, b)is at least as big as the number of genres inaalone, and no bigger than the number of genres inaplus the number inb.
Part 5Copying Over your Check blocks
For the autograder to work, your hw4-tests.arr file should contain ONLY checks for the functions we told you to write (not helpers). Use your favorite AI tool to copy them over from hw4-code.arr, using this prompt:
A prompt you can use
I have attached or included a Pyret file named hw4-code.arr. I need a new Pyret file
named hw4-tests.arr that contains only some of its tests, as follows.
Copy over every check block that tests any of these functions:
num-customers, num-real-orders, num-genres-both, big-order-customers,
loyal-customers, new-genres, big-order-ok, loyal-ok, new-genres-ok
A check block that also tests other functions should be copied, but with the
lines that test those other functions removed.
Do not copy checks that only test helper functions. Do not copy any function
definitions. If a copied check refers to a table, list, or other value that I
defined myself in hw4-code.arr, copy that definition too, above the checks
that use it. Do not copy definitions given in the starter code (sample1, sample2).
If a definition refers to one of my helper functions, tell me instead of
copying it.
Do not change the text of any check, and do not add new tests.
When you are done, list every check you copied, every definition you
included, and every check you skipped.Grading
Grading for this assignment will consist of:
- Us running your functions against our tests (via the autograder)
- Us using your
checkblocks to try to detect our buggy solutions - Us inspecting your
docstrings to make sure they are reasonable - A code review where we ask you about your functions, possible changes to them, and your plans for testing (as reflected in your tests)
Handin
Hand in these files, together, in one submission:
hw4-code.arrhw4-tests.arr
Make sure you have put links to any required chat transcripts at the top of your file. Submit to Gradescope. You may submit as many times as you like before the deadline; we grade the last submission.
Comments or concerns?
If you have comments or concerns, submit them on the anonymous feedback form. Sign in with your Brown Google account to open it; your email address is not recorded.