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.
How do I match the start of a string or the end of a string?
If you update the csc151 library, you’ll now have
(rex-start-of-string)and(rex-end-of-string). Here’s an example.
> (rex-find-matches (rex-char-range #\a #\z) "blah")
'("b" "l" "a" "h")
> (rex-find-matches (rex-concat (rex-start-of-string)
(rex-char-range #\a #\z))
"blah")
'("b")
> (rex-find-matches (rex-concat (rex-char-range #\a #\z)
(rex-end-of-string))
"blah")
'("h")
Do you realize how much reading you gave us for today?
Too much! But at least there’s no reading for tomorrow.
One solution.
(define longest-string
(lambda (lst)
(cond
[(null? (cdr lst))
(car lst)]
[(> (string-length (car lst))
(string-length (longest-string (cdr lst))))
(car lst)]
[else
(longest-string (cdr lst))])))
What do you notice?
There are two calls to (longest-string (cdr lst)).
What is the effect?
Side conversation
;;; (num->words nums) -> listof string?
;;; nums : listof natural?
;;; Make a list of words, each of whose length is the corresponding
;;; value in nums.
(define nums->words
(lambda (nums)
(map (section make-string <> #\a)
nums)))
What is (num->words ‘(1 4 2))
> (nums->words '(1 4 2))
'("a" "aaaa" "aa")
We’ve defined increasing as follows.
;;; increasing : listof string?
;;; A list of strings in increasing length order
(define increasing
(nums->words (range 0 22)))
What value do you expect increasing to have?
> increasing
'("" "a" "aa" ... "aaaaaaaaaaaaaaaaaaaaa")
> (time (longest-string increasing))
cpu time: 204 real time: 205 gc time: 0
"aaaaaaaaaaaaaaaaaaaaa"
> (string-length "aaaaaaaaaaaaaaaaaaaaa")
What is decreasing?
And when we try to do it …
> (time (longest-string decreasing))
cpu time: 0 real time: 1 gc time: 0
"aaaaaaaaaaaaaaaaaaaaa"
Whooa! That’s a lot faster. Why?
If we reach the else branch every time, we’re doubling. But we’re not doubling once, we’re doubling at every time through the recursive calls. 2^21 times the work.
(longest-string *list-of-length-21*)
2 * (longest-string *list-of-length-20*)
2 * 2 * (longest-string *list-of-length-19*)
2 * 2 * 2 * (longest-string *list-of-length-18*)
If the hypohtesis is true, and I increase the range by one, the time shoudl be twice as much.
> (let ([lst (nums->words (range 0 23))])
(time (longest-string lst)))
cpu time: 430 real time: 433 gc time: 0
"aaaaaaaaaaaaaaaaaaaaaa"
> (let ([lst (nums->words (range 0 24))])
(time (longest-string lst)))
cpu time: 868 real time: 873 gc time: 0
"aaaaaaaaaaaaaaaaaaaaaaa"
> (let ([lst (nums->words (range 0 25))])
(time (longest-string lst)))
cpu time: 1689 real time: 1701 gc time: 0
"aaaaaaaaaaaaaaaaaaaaaaaa"
How can we fix it? How can we make longest-string better?
(define longest-string
(lambda (lst)
(let ([longest-in-cdr (longest-string (cdr lst))])
(cond
[(null? (cdr lst))
(car lst)]
[(> (string-length (car lst))
(string-length longest-in-cdr))
(car lst)]
[else
longest-in-cdr]))))
(define longest-string
(lambda (lst)
(cond
[(null? (cdr lst))
(car lst)]
[else
(let ([longest-in-cdr (longest-string (cdr lst))])
(cond
[(> (string-length (car lst))
(string-length longest-in-cdr))
(car lst)]
[else
longest-in-cdr]))])))
> (time (longest-string increasing))
cpu time: 0 real time: 0 gc time: 0
"aaaaaaaaaaaaaaaaaaaaaa"
> (time (longest-string decreasing))
cpu time: 0 real time: 0 gc time: 0
"aaaaaaaaaaaaaaaaaaaaaa"
> (let ([lst (nums->words (range 0 240))])
(time (longest-string lst)))
cpu time: 1 real time: 0 gc time: 0
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
The morals of the story.
let (or other technique) to avoid it.And make your code cleaner.
(define longest-string
(lambda (lst)
(if (null? (cdr lst))
(car lst)
(let ([longest-in-cdr (longest-string (cdr lst))])
(if (> (string-length (car lst))
(string-length longest-in-cdr))
(car lst)
longest-in-cdr)))))
longest-in-cdr is just a look up into a table of the
name/value pairs. [Mental Model 2]How do we write list-length?
;;; (list-length lst) -> integer?
;;; lst : list?
;;; Determines the number of elements in lst
(define list-length
(lambda (lst)
(if (null? lst) ; Check for base case
0 ; Return a base value
(+ 1 (list-length (cdr lst))))))
(test-equal? "um ... a normal list of integers"
(list-length '(1 2 3 4 5))
5)
(test-equal? "empty list"
(list-length null)
0)
(test-equal? "integers in reverse order"
(list-length '(6 5 4 3 2 1))
6)
(test-equal? "a really long list"
(list-length (make-list 10000 1))
10000)
Let’s figure out how to deal with the recursive call. Let’s suppose it works for the smaller list
Normal (direct)
(define fun
(lambda (params)
(if (base-case-test? params)
base-case
(combine params (fun (simplify params))))))
Convert to tail recursive
(define fun-helper
(lambda (params so-far)
(if (base-case-test? params)
so-far
(fun-helper (simplify params) (combine params so-far)))))
(define fun
(lambda (params)
(fun-helper params base-case)))
Let’s try again, this time making list-length tail-recursive.
(define list-length-helper
(lambda (lst so-far)
(if (null? lst)
so-far
(list-length-helper (cdr lst) (+ 1 so-far)))))
(define list-length
(lambda (lst)
(list-length-helper lst 0)))
> (time (list-length-old ben))
cpu time: 8187 real time: 11445 gc time: 5201
1000000
> (time (list-length ben))
cpu time: 28 real time: 29 gc time: 0
1000000
Option one: DON’T USE THIS
(define one-element-list?
(lambda (lst)
(= (length lst) 1)))
Option two:
(define one-element-list?
(lambda (lst)
(null? (cdr lst))))
Option three
(define one-element-list?
(o null? cdr))
Option four
(define one-element-list?
(lambda (lst)
(and (not (null? lst))
(null? (cdr lst)))))
reverseTail recursion will be our first approach.
;;; (rev lst) -> list?
;;; lst : list?
;;; Reverse lst.
(define rev
(lambda (lst)
(rev-helper lst null)))
(define rev-helper
(lambda (lst so-far)
(if (null? lst)
so-far
(rev-helper (cdr lst) (cons (car lst) so-far)))))
(test-equal? "base case"
(rev '())
'())
(test-equal? "That standard list of integers"
(rev '(1 2 3))
'(3 2 1))
(test-equal? "An excessive case"
(rev (range 1 10000))
(range 9999 0 -1))
powers-of-twoDetour away from tail recursion, but still use a helper.
;;; (powers-of-two n) -> listof integer?
;;; n : natural?
;;; Make a list of all integral powers of 2 strictly less than n,
;;; starting with 1
(define powers-of-two
(lambda (n)
(powers-of-two-helper n 1)))
;;; (powers-of-two-helper n k) -> listof integer?
;;; n : natural?
;;; k : natural?
;;; Make a list of all integral powers of 2 strictly less than n,
;;; starting with k.
(define powers-of-two-helper
(lambda (n k)
(if (>= k n)
null
(cons k (powers-of-two-helper n (* k 2))))))
What do you expect for (powers-of-two-helper 128 128)?
(test-equal? "k equals n"
(powers-of-two-helper 128 128)
'())
(test-equal? "k exceeds n"
(powers-of-two-helper 60 64)
'())
range;;; (my-range start finish) -> listof integer?
;;; start : integer?
;;; finish : integer?
;;; Compute the list (start start+1 start+2 ... finish-1).
(define my-range
(lambda (start finish)
(if (>= start finish)
'()
(cons start (my-range (+ start 1) finish)))))
(test-equal? "empty range at 0"
(my-range 0 0)
'())
(test-equal? "empty range at 5"
(my-range 5 5)
'())
(test-equal? "the normal list"
(my-range 1 6)
'(1 2 3 4 5))
We’ve written it “counting up”. We could also use tail recursion to count down.
appendlist-reffibonacci