Assignment: 5: Due: Language Level: Files To Submit: Practice Exercises
Assignment: 5: Due: Language Level: Files To Submit: Practice Exercises
Assignment: 5
Due: Tuesday, October 27nd at noon (Waterloo time)
Language level: Beginning Student with List Abbreviations
Files to submit: not-lists.rkt, groceries.rkt, sillystring.rkt
Practice exercises: HtDP 6.4.2, 6.5.2, 7.3.1, 17.1.2, 17.2.2, 17.6.2, and 17.8.4
• Make sure you read the OFFICIAL A05 post on Piazza for the answers to frequently
asked questions.
• Unless stated otherwise, all policies from Assignment 04 carry forward.
• This assignment covers material up to the end of Module 10.
• The only built-in functions and special forms you may use are listed below. If a built-in
function or special form is not in the following list, you may not use it:
• Remember that basic tests are meant as sanity checks only; by design, passing them should
not be taken as any indication that your code is correct, only that it has the right form.
• Unless the question specifically says otherwise, you are always permitted to write helper
functions to perform any task. You may use any constants or functions from any part of a
question in any other part.
1. Lists? We don’t need no stinking lists! Recall that we defined a (listof X) as follows:
;; A (listof X) is one of:
;; * empty
;; * (cons X (listof X))
We then used the built-in functions first and rest to extract parts of the list, and the built-in
function cons to make a list one longer.
Here we will create a structure that is able to store arbitrarily long data, just like a list.
Place your solution in the file not-lists.rkt
For this question, use the following data definition:
For example,
(check-expect (ls-length
(make-ls "!" (make-ls 'huh (make-ls 42 'nothing)))) 3)
2. To keep track of items sold at the local family-run store, we need to be able to describe each
item. Each item has a department, name, price per package (in dollars), and mass per package
(in grams).
Place your solution to this question in the file groceries.rkt.
We use the following data definition:
(define-struct grocery (dept name cost mass))
;; A Grocery is a (make-grocery Str Str Num Num)
;; Requires: cost >= 0, mass > 0.
Here are two Stores, examples of a database of products we might deal with:
(define try-n-save
(list (make-grocery "produce" "apple" 2.49 600)
(make-grocery "seed" "rice" 0.95 1000)
(make-grocery "dairy" "milk" 3.99 4000)
(make-grocery "seed" "pinto" 2.49 500)
(make-grocery "produce" "potato" 2.99 5000)
(make-grocery "chips" "potato" 1.99 250)
(make-grocery "chips" "corn" 1.99 275)
(make-grocery "seed" "wheat" 0.49 500)
(make-grocery "produce" "banana" 0.69 450)
(make-grocery "dairy" "cheese" 6.49 900)
(make-grocery "chips" "banana" 1.99 50)
(make-grocery "produce" "peach" 3.99 400)
(make-grocery "seed" "lentil" 2.99 800)
(make-grocery "produce" "corn" 0.99 100)
(define kwik-e-mart
(list (make-grocery "seed" "rice" 0.38 400)
(make-grocery "can" "corn" 4.00 400)
(make-grocery "seed" "pinto" 2.49 500)
(make-grocery "produce" "apple" 2.99 400)
(make-grocery "can" "creamed eels" 2.19 350)
(make-grocery "produce" "pineapple" 3.17 250)))
We want to be able to query databases like these to find products with certain features.
least ten items coming from at least three departments. At least two departments
should have at least two items in them.
For testing of functions in the rest of this question, you may use any of try-n-save,
kwik-e-mart, student-shop, and other values you create.
(b) [5% Correctness] Intervals. We want to be able to tell if a number is within an interval,
that is, at least some lower bound, and at most another. We define an interval as follows:
(define-struct interval (lo hi))
;; An Interval is a (make-interval (anyof 'dontcare Num)
;; (anyof 'dontcare Num))
(check-expect (in-interval? 42
(make-interval 'dontcare 'dontcare)) true)
(check-expect (in-interval? 34
(make-interval 35 'dontcare)) false)
(check-expect (in-interval? 34
(make-interval 'dontcare 35)) true)
We say that a Str and a StrPatt “match” if the StrPatt is 'dontcare, or if the Str and
the StrPatt are identical.
Now we are ready to describe our queries.
(define-struct query (dept name cost mass))
;; A GroceryQuery is a
;; (make-query StrPatt StrPatt Interval Interval)
A Grocery satisfies a GroceryQuery if all the string fields match, and each number field
is in the corresponding interval.
Write a function find-matches that consumes a (listof Grocery) and a
Exercise
GroceryQuery. It produces a list containing the items from the list that satisfy the
GroceryQuery. Leave the items in the same order they appear in the consumed list.
See the following example queries.
Whereas this shows everything from any department that is listed as "corn":
(check-expect
(find-matches try-n-save (make-query 'dontcare "corn"
(make-interval 'dontcare 'dontcare)
(make-interval 'dontcare 'dontcare)))
(list (make-grocery "chips" "corn" 1.99 275)
(make-grocery "produce" "corn" 0.99 100)
(make-grocery "seed" "corn" 4.99 850)))
And this shows everything from the "seed" department that costs no more that $3.00
and comes is a package of at least 600 g.
(check-expect
(find-matches try-n-save (make-query "seed" 'dontcare
(make-interval 'dontcare 3.00)
(make-interval 600 'dontcare)))
(list
(make-grocery "seed" "rice" 0.95 1000)
(make-grocery "seed" "lentil" 2.99 800)))
(d) [10% Correctness] Sorting. Now we want to organize the product available for sale.
Exercise
values, sorted alphabetically by department, then alphabetically by name.
Use one or more of the predicates string=?, string<?, string<=?, string>?,
string>=? to determine if strings are in order. Their behaviour defines the desired
order for all Str.
For example,
(check-expect (sort-dept-name try-n-save)
(list
(make-grocery "chips" "banana" 1.99 50)
(make-grocery "chips" "corn" 1.99 275)
(make-grocery "chips" "potato" 1.99 250)
(make-grocery "dairy" "cheese" 6.49 900)
(make-grocery "dairy" "kefir" 5.99 1000)
(make-grocery "dairy" "milk" 3.99 4000)
(make-grocery "produce" "apple" 2.49 600)
(make-grocery "produce" "banana" 0.69 450)
(make-grocery "produce" "corn" 0.99 100)
(make-grocery "produce" "peach" 3.99 400)
(make-grocery "produce" "potato" 2.99 5000)
(make-grocery "seed" "corn" 4.99 850)
(make-grocery "seed" "lentil" 2.99 800)
(make-grocery "seed" "pinto" 2.49 500)
(make-grocery "seed" "rice" 0.95 1000)
(make-grocery "seed" "wheat" 0.49 500)))
(e) [15% Correctness] The Price is Right. If we have more than one Store, we want to
be able to find which items are available at both stores so we can comparison shop.
Write a function overlap. It consumes two Store, and produces a Store, containing
Exercise
only those items available in both Stores. Choose the item that is cheaper, per
gram. For items with equal cost per gram, we prefer the smaller package.
The result should be sorted by department and name, as above. Hint: sort the lists
before you compare them.
For example,
(check-expect
(overlap kwik-e-mart try-n-save)
(list
(make-grocery "produce" "apple" 2.49 600) ; Buy cheaper.
(make-grocery "seed" "pinto" 2.49 500) ; Same price and size.
(make-grocery "seed" "rice" 0.38 400))) ; Same price; buy smaller.
(f) [10% Correctness] Inflation. Now we want to be able to change the prices of certain
items in our store.
Exercise
GroceryQuery should have their price changed by the ratio, rounded to the nearest
0.01. The order of items should remain the same.
Use the built-in round function to do the rounding. You will need to do a little
arithmetic to make this work.
3. Silly String. We can store information in many different ways. With lists, we store a first
item, and the rest of the items. Here we will store the contents of a Str, in three parts:
For example, in "Babbage", the parts are #\B, "abbag", and #\e.
If we store the middle characters using the same kind of structure, we have a recursive data
structure that stores the contents of a Str.
Place your solution in the file sillystring.rkt.
We will use the following data definition:
(define-struct silly-string (first middle last))
;; A SillyStringStruct is a (make-silly-string Char SillyStr Char)
#\e))
(check-expect (sillify "Lovelace")
(make-silly-string
#\L
(make-silly-string
#\o
(make-silly-string
#\v
(make-silly-string
#\e
empty
#\l)
#\a)
#\c)
#\e))
(make-silly-string
#\v
(make-silly-string
#\e
empty
#\l)
#\a)
#\c)
#\e))
"Lovelace")
Do not convert to a Str. You must directly use the recursive structure of the
!
SillyStr.
(define (sum-first n)
(cond
[(zero? n) 0]
[else (+ n (sum-first (sub1 n)))]))
To prove this program correct, we need to show that, for all natural numbers n, the result of
evaluating (sum-first n) is ∑ni=0 i. We prove this by induction on n.
Base case: n = 0. When n = 0, we can use the semantics of Racket to evaluate (sum-first 0) as
follows:
(sum-first 0) ; =>
(cond [(zero? 0) 0][else ...]) ; =>
(cond [true 0][else ...]) ; =>
0
(sum-first n) ; =>
(cond [(zero? n) 0][else ...]) ;(we know n > 0) =>
(cond [false 0][else ...]) ; =>
(cond [else (+ n (sum-first (sub1 n)))]) ; =>
(+ n (sum-first (sub1 n)))
Now we use the inductive hypothesis to assert that (sum-first (sub1 n)) evaluates to s = ∑n−1
i=0 i. Then
n−1 n
(+ n s) evaluates to n + ∑i=0 i, or ∑i=0 i, as required. This completes the proof by induction.
Use a similar proof to show that, for all natural numbers n, (sum-first n) evaluates to (n2 + n)/2.
Note: Summing the first n natural numbers in imperative languages such as C++ or Java would
be done using a for or while loop. But proving such a loop correct, even such a simple loop, is
considerably more complicated, because typically some variable is accumulating the sum, and its
value keeps changing. Thus the induction needs to be done over time, or number of statements
executed, or number of iterations of the loop, and it is messier because the semantic model in these