This class will be recorded! Its use is limited to members of the class. Please do not share with others.
Approximate overview
Events
I’m not sure if all of these links are correct. Let me know if any are not.
We’ll be talking about the mini-project after the quiz, so no questions on the mini-project until then.
We’ll be talking about recursion after we talk about MP3, so no questions on recursion.
Is the SoLA really Thursday?
Yes. But I’ll give you two days because of the confusion.
How do we sign up for visiting the drop-in-tutors in person?
Step 1: Go to the CS Team
Step 2: Go to the Evening Tutoring Channel
Step 3: Click on “Files”
Step 4: Click on “Sign Up For Lab Space”
Make sure to Self-Gov the signup.
What are the approximate answers to this quiz?
(test-equal? message something something-else)
Do we have to make a test suite?
No.
Do we have to add a message to each test-equal?
Yes.
Do we need to have a message for test-equal?
You need something for the
messageargument. It could “”.
It’s nice to have something so that you can tell which test failed.
What will the style problem on the second SoLA look like?
Easier.
We’ll probably add
letto the mix.
What’s the difference between test-equal? and check-equal?? Are
there situations we should use the latter?
For what we know, they are equivalent, except for parameter order.
I prefer that you use
test-equal?
You might use
check-equal?in a test suite where you don’t want to have to write the message.
What is the epsilon in check=?
Inexact numbers are approximated in Racket.
So when we do computations that should return the same numeric value, but might instead return something close.
(test-= (sqr (sqrt 2)) 2.0 ...)
They won’t be equal, but they are pretty close.
We should specify what answers are close enough.
(test-= (sqr (sqrt 2)) 2.0 0.00001)says “It’s okay as long the two numbers are within 1/10000 of each other.”
If we were dealing with something like
(test-= (sqr (sqrt (expt 2 -100))) (expt 2 -100) (expt 1 -103))
It’s supposed to be “how close is ‘close enough’”?
> (test-= "small number" (sqr (sqrt (expt 2 -100))) (expt 2 -100) (expt 10 -103))
> (test-= "medium number" (sqr (sqrt 2)) 2 (expt 10 -103))
--------------------
medium number
. FAILURE
name: check-=
location: interactions from an unsaved editor:56:2
params:
'(2.0000000000000004
2
1/10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)
--------------------
What if we don’t get credit on a LA?
Take it again on the next SoLA.
There are five SoLAs all together.
The fifth one has no new material.
You should also talk to an evening tutor or an individual tutor or SamR or … about the problem to understand the topic and the particulars.
I am serious that you should use the “document, then write tests, then implement, then write more tests” approach. There are multiple reasons to do so.
Why document?
Why write tests before code?
Why write tests after code?
Where should I document?
Everywhere!
Broadly in acknowledgements.
Particuarly in particular places.
Should we turn in one mini-project as a group?
No.
You should not be working that closely together.
Do we have to credential people?
No. “I know this person is smart because the second and third letters of their name are A.”
What if we don’t know someone’s name.
Explain context. “I got help from the Sunday evening tutor.”
I see many of you writing long, complex, hard-to-read expressions. Remember, other human beings are supposed to be able to read your code. So decompose!
How?
let or let*.Why?
Do we have to document each variable in the let?
It’s just good practice. Good names may suffice.
Goal: Write (count-words words filename).
Decompose: I know how to read a string from a file, so I should
implement count-words with (count-words-in-string words str).
;;; (count-words-in-string words str) -> listof (listof word/number)
;;; words : listof string?
;;; str : string?
;;; Count how many times each word in words appears as a separate
;;; word in str. Return something of the form
;;; '(("word" count) ...)
Hmmm … Giant strings are hard to think about. So perhaps I should
think about breaking count-words in string into: (a) separate a string
into a second list of words and (b) (count-words-in-list words list-of-words).
Can I separate a string into a list of words? I think I did that on the rex lab. I’ll cite my partner.
Or “I seem to recall Sam posting that somewhere. I’ll copy it and cite him.”
;;; (count-words-in-list words list-of-strings) -> listof (listof string/number)
;;; words : listof string?
;;; list-of-strings : listof string?
;;; Count how many times each word in words appears as an element of
;;; list-of-strings. Return something of the form
;;; '(("word" count) ...)
I’ll need to count each word separately, so I should write a count-word
procedure and then I’ll map it over my list of words.
;;; (count-one-word word list-of-strings) -> listof word/number
;;; words : listof string?
;;; list-of-strings : listof string?
;;; Count how many times word appears as an element of list-of-strings.
;;; Returns something of the form '(word count)
That’s easy enough for me to write based on things I know, such as
tally (or tally-value) and one or two other things.
Does it ever hurt to write too many procedures?
In some cases, it takes you more time.
In some cases, it makes your code slower.
But it’s a good first step in many cases.
Could you explain using let outside of lambda?
(define proc
(let ([binding one]
[bind two]
...)
(lambda (...)
...)))
Idea: We are generating a bunch of names that we can use within the lambda.
We can think of DrRacket as evaluating the
letand plugging everything in.
Could you explain about using lambda within the let?
;;; (add-to-all val lst) -> listof number?
;;; val : number?
;;; lst : listof number?
;;; Adds val to each element of lst, producing a new list.
(define add-to-all
(lambda (val lst)
(let ([helper (lambda (x) (+ val x))])
(map helper lst))))
Some of you asked why your code runs so slowly. The most common reasons are
string->list and then list->string in quick succession.list-refHere’s one easy thing to fix: Reading from a file is slow. Really slow. So try not to read from the same file multiple times. Here are some changes you might make.
If you’ve written (tally-word-in-file word filename) which counts
how often word appears in filename, you might to use
(tally-word-in-list word lst) or (tally-word-in-string word
str) instead.
If you find yourself repeatedly reading the Dale-Chall list, name it.
;;; dale-chall-words : listof string?
;;; All the Dale-Chall easy words, in a convenient list.
(define dale-chall-words (file->lines "DaleChall.txt"))
Or perhaps
(define easy-word?
(let ([dale-chall-words (file->lines "DaleChall.txt")])
(lambda (word)
...)))
I’ll ask you to add some of your own.
sectionMany of you are confused by section. I posted this in the Q&A
channel, but it’s long, so I’m rewriting it here. It also gives
you a chance to ask questions.
Section is one of those things that don’t make sense until something clicks. Let me try to see what I can do to help.
section as “another way to write procedures as an
alternative to lambda”.(define add2 (lambda (x) (+ 2 x))). All add2 does
is call + with a “canned” value (2) and add2’s parameter (x).section. So we could also write
(define add2 (section + 2 <>)).But when does the <> (“diamond”) get filled in?
map or filter or …(lambda (x) (+ 2 x)) is
“a procedure that takes one input, x, and adds 2 and that input”.(section + 2 <>) as “a procedure that takes one input
and adds 2 and that input”. It’s just a more concise way of writing
the lambda expression. And, to many people, it’s easier to read.sectionWe might start by defining a helper procedure that determines whether At this stage of your career, you might find it easier to get to section in a series of steps. For example, consider the following procedure.
;;; (extract-bigger-than lower-bound vals) -> list-of integer?
;;; lower-bound : integer?
;;; vals : listof integer?
;;; Extract all the values from vals that are strictly bigger
;;; than lower-bound.
We might start by defining a helper procedure that determines whether a single value is bigger than val and then filter the list using that procedure.
(define extract-bigger-than
(lambda (lower-bound vals)
(let ([bigger-than-lower-bound
(lambda (x) (> x lower-bound))])
(filter bigger-than-lower-bound vals))))
Let’s verify that it works. (We should use tests, but sometimes it’s nice to see the output.)
> (extract-bigger-than 5 (list 9 1 2 18 3 5 4 -2 7))
'(9 18 7)
Yes, that’s what I wanted.
lambda to sectionLooking at bigger-than-val, I see that it takes the form appropriate
for section. That is, we’re just filling in one of the two parameters
to the > procedure.
I can use section to make the code a little bit shorter.
(define extract-bigger-than
(lambda (lower-bound vals)
(let ([bigger-than-lower-bound (section > <> lower-bound)])
(filter bigger-than-lower-bound vals))))
And I’ve confirmed that it works using the tests I’ve written. (Don’t worry, I’ll include those.)
letI know that when I have (let ([name val]) exp), I can just substitute
val for name in exp. So let’s do that.
(define extract-bigger-than
(lambda (lower-bound vals)
(filter (section > <> lower-bound) vals)))
And this passes all my tests.
Here are the tests I wrote. Note that I’ve included some edge cases (all values smaller than the bound, all larger, empty list, negative bound).
(test-equal? "extract-bigger-than: empty list"
(extract-bigger-than 5 '())
'())
(test-equal? "extract-bigger-than: all values <= bound"
(extract-bigger-than 5 '(4 1 3 5 1 2 -5))
'())
(test-equal? "extract-bigger-than: assorted values"
(extract-bigger-than 5 (list 9 1 2 18 3 5 4 -2 7))
'(9 18 7))
(test-equal? "extract-bigger-than: min value is negative"
(extract-bigger-than -10 '(-11 5 -12 -4 -5 20 -10))
'(5 -4 -5 20))
(test-equal? "extract-bigger-than: really long list"
(extract-bigger-than 10 (range 11 1000))
(range 11 1000))
If pressed to do more, I might try lists with values equal to each other and lists consisting of values equal to the bound.
Things may make more sense if we look at how to trace section and
map (and section+map).
When we encounter an expression of the form ((section proc <> v) exp),
we
exp. (So far, the only times we don’t evaluate the
expression is in conditionals and in the body of lambdas.)(proc value-of-exp v).That is
((section proc <> v) val-of-exp)
--> (proc val-of-exp v)
Similarly
((section proc v <>) val-of-exp)
--> (proc v val-of-exp)
And
((section proc <> v <>) val1 val2)
--> (proc val1 v val2)
For example,
(define plus2 (section + <> 2))
(plus2 (* 3 5))
; Evaluate the (* 3 5)
--> (plus2 15)
; Replace the plus2 by its definition.
--> ((section + <> 2) 15)
; Use the section replacement rule
--> (+ 15 2)
; Evaluate as normal
--> 17
Where does the diamond or hole go? Wherever you want the parameter to go.
mapThe basic rule for map is “_Replace (map proc '(v1 v2 ...)) with
(list (proc v1) (proc v2) ...). Let’s try a simple example.
(map sqr (list (+ 1 2) (* 3 5) 8))
; Evaluate each elment in the list
--> (map sqr (list 3 (* 3 5) 8))
--> (map sqr (list 3 15 8))
--> (map sqr '(3 15 8))
; Apply the map rule
--> (list (sqr 3) (sqr 15) (sqr 8))
; Evaluate each element in the list
--> (list 9 (sqr 15) (sqr 8))
--> (list 9 225 (sqr 8))
--> (list 9 225 64)
; Finish the list
--> '(9 225 64)
Note that most of you are at the point in which you can evaluate all the elements of the list simultaneously in your traces. I just write it step by step for clarity (and to avoid my own mistakes).
(map sqr (list (+ 1 2) (* 3 5) 8))
--> (map sqr (list 3 15 8))
--> (list (sqr 3) (sqr 15) (sqr 8))
--> (list 9 225 64)
--> '(9 225 64)
map + sectionWhat about using map and section together? We just apply
the rules for each.
(map (section - <> 2) '(1 5 3))
; Apply the map rule
--> (list ((section - <> 2) 1) ((section - <> 2) 5) (section - <> 2) 3)
; Apply the section rule in the first expression
--> (list (- 1 2) ((section - <> 2) 5) (section - <> 2) 3)
; Evaluate the first expression
--> (list -1 ((section - <> 2) 5) (section - <> 2) 3)
; Apply the section rule in the second expression
--> (list -1 (- 5 2) (section - <> 2) 3)
; Evaluate the second expression
--> (list -1 3 ((section - <> 2) 3)
; Apply the section rule in the third expression
--> (list -1 3 (- 3 2))
; Evaluate the third expression
--> (list -1 3 1)
; Wrap up
--> '(-1 3 1)
We’re using tally or tally-value a lot on this procedure. Let’s
explore how it might work, or at least how we might trace it conceptually.
We’ll assume that it has to work step-by-step, looking at each value
in turn.
(tally-value '(b a c a b a) 'a)
; The b is not the same as a, so it won't contribute to our overall
; tally. Throw away.
; We should go on to the next thing
--> (tally-value '(a c a b a) 'a)
; Compare the a to a. They are the same.
; Add 1 to the count
; And then move on to the rest of the list
--> (+ 1 (tally-value '(c a b a) 'a))
; Compare c to a. No. Continue on
--> (+ 1 (tally-value '(a b a) 'a))
; Compare a to a. Same. Add 1 and continue
--> (+ 1 (+ 1 (tally-value '(b a) 'a)))
; Compare b to a. Different. Throw away the b and continue
--> (+ 1 (+ 1 (tally-value '(a) 'a)))
; Compare a to a. Same. Add 1 and continue
--> (+ 1 (+ 1 (+ 1 (tally-value '() 'a))))
; There are 0 a's in the empty list. We're done.
--> (+ 1 (+ 1 (+ 1 0)))
--> (+ 1 (+ 1 1))
--> (+ 1 2)
--> 3
Question: Can we write tally-value?
Stay tuned to our next class?
Why might I be unhappy with these?
``` => (+ 5 (+ 8 (sum (list 2)))) => (+ 5 (+ 8 (if (null? (list 2))))) 0 (+ (car (list 2)) (sum (cdr (list 2)))))) => (+ 5 (+ 8 #f)) 0 (+ (car (list 2)) (sum (cdr (list 2)))))) => (+ 5 (+ 8 (+ 2 (sum ‘()))))