My notes on this chapter are short because I found it very easy, which I expected. I’ve looked basic recursion stuff before in detail, so this is more of a review.
This chapter introduces the “combining” method of recursion with an example of a procedure that does the following:
The basic thing they do is work their way up, first start with a procedure that can handle the one letter case, then the two letter case, then the three letter case, and then starting to look for patterns.
Every recursive procedure has the following attributes:
1. a recursive case where the procedure calls itself, and
2. a base case that the procedure can address without calling itself
3. each recursive call gets us closer to the base case
for the solution for downup
:
1
2
3
4
5
|
(define (downup wd)
(if (= (count wd) 1)
(se wd)
(se wd (downup (bl wd)) wd)))
|
the recursive case is when the count = 1, in which case downup
returns the wd
. The recursive case is when count > 1, in which case downup
makes a recursive call to itself while also returning wd
on either side of the recursive call. And the recursive case gets closer to the base case due to the bl wd
, which reduces the length of wd
by 1.
Boring Exercises
✅ Exercise 11.1
Write downup4 using only the word and sentence primitive procedures.
1
2
3
4
5
6
7
8
9
|
(define (downup4 wd)
(se wd
(bl wd)
(bl (bl wd))
(first wd)
(bl (bl wd))
(bl wd)
wd))
|
I did some extra analysis and testing beyond what the exercise required.
The domain of this procedure is words (including numbers) or sentences. The range is a sentence with the characters from a word displayed in the “downup” fashion, or a sentence with words being “downup”‘ed like the characters of a word. This will be clearer with examples.
downup4
works correctly with a 4-letter word, which is the intended case:
It does not work correctly with 3 letter words. Note the unnecessary instances of 'h
It also does not work correctly with 5 letter words. Note the jump from 3-character cak
to one-character c
:
If you use downup4
on a sentence, the words are treated like the individual characters in the previous examples:
so it goes down from potato ninja tomato hat
to potato ninja tomato
to potato ninja
to potato
and then back up.
✅ Exercise 11.2
When you teach a class, people will get distracted if you say “um” too many times. Write a
count-ums
that counts the number of times “um” appears in a sentence:
1
2
3
4
|
> (count-ums '(today um we are going to um talk about the combining um method))
3
|
Here are some special-case
count-ums
procedures for sentences of particular lengths:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
(define (count-ums0 sent)
0)
(define (count-ums1 sent)
(if (equal? 'um (first sent))
1
0))
(define (count-ums2 sent)
(if (equal? 'um (first sent))
(+ 1 (count-ums1 (bf sent)))
(count-ums1 (bf sent))))
(define (count-ums3 sent)
(if (equal? 'um (first sent))
(+ 1 (count-ums2 (bf sent)))
(count-ums2 (bf sent))))
|
Write
count-ums
recursively.
Note: this was my non-recursive solution for this problem back in chapter 8:
1
2
3
4
5
6
|
(define (um? wd)
(if (equal? wd 'um) 1 0))
(define (count-ums sent)
(accumulate + (every um? sent)))
|
I think a solution has to handle the following things:
1. Seeing if the sentence is empty (the base case)
2. Seeing if a particular word is an um
3. Adding 1 if a word is an um
4. Not adding 1 if a word is not an um
5. Recursively calling count-ums
with a shorter version of the sentence regardless of whether the word is an um or not
So based on the examples in the book this was my first attempt at trying to meet these criteria:
1
2
3
4
5
6
|
(define (count-ums sent)
(if (empty? sent) 0
(if (equal? 'um (first sent))
(+ 1 (count-ums (bf sent)))
(count-ums (bf sent)))))
|
it works on test cases. I do wonder if there might be a more elegant way to organize it though. Seems okay though. Here’s a tree:
After a while I thought of a version with a helper procedure:
1
2
3
4
5
6
7
|
(define (um-checker wd)
(if (equal? 'um wd) 1 0))
(define (count-ums sent)
(if (empty? sent) 0
(+ (um-checker (first sent)) (count-ums (bf sent)))))
|
I like this version better, since it avoids nesting an if
in the main part of the program.
✅ Exercise 11.3
Write a procedure
phone-unspell
that takes a spelled version of a phone number, such asPOPCORN
, and returns the real phone number, in this case7672676
. You will need a helper procedure that translates a single letter into a digit:
1
2
3
4
5
6
7
8
9
10
11
12
|
(define (unspell-letter letter)
(cond ((member? letter 'abc) 2)
((member? letter 'def) 3)
((member? letter 'ghi) 4)
((member? letter 'jkl) 5)
((member? letter 'mno) 6)
((member? letter 'prs) 7)
((member? letter 'tuv) 8)
((member? letter 'wxy) 9)
(else 0)))
|
Here are some some special-case
phone-unspell
procedures:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
(define (phone-unspell1 wd)
(unspell-letter wd))
(define (phone-unspell2 wd)
(word (unspell-letter (first wd))
(unspell-letter (first (bf wd)))))
(define (phone-unspell3 wd)
(word (unspell-letter (first wd))
(unspell-letter (first (bf wd)))
(unspell-letter (first (bf (bf wd))))))
|
Write
phone-unspell
recursively.
I changed their helper procedure so that it could handle capital letters:
1
2
3
4
5
6
7
8
9
10
11
|
(define (unspell-letter letter)
(cond ((member? letter 'abcABC) 2)
((member? letter 'defDEF) 3)
((member? letter 'ghiGHI) 4)
((member? letter 'jklJKL) 5)
((member? letter 'mnoMNO) 6)
((member? letter 'prsPRS) 7)
((member? letter 'tuvTUV) 8)
((member? letter 'wxyWXY) 9)
(else 0)))
|
and here is my solution:
1
2
3
4
5
6
|
(define (phone-unspell wd)
(if (empty? wd) ""
(word
(unspell-letter (first wd))
(phone-unspell (bf wd)))))
|
Test:
1
2
3
|
(phone-unspell 'POPCORN)
7672676
|
Real Exercises
🤔Exercise 11.4
Who first said “use what you have to get what you need”?
Legit could not figure out the answer to this one despite heavy googling.
UPDATE: A commenter below says the answer is General Giap and links this
Mystery solved!
✅ Exercise 11.5
Write a procedure
initials
that takes a sentence as its argument and returns a sentence of the first letters in each of the sentence’s words:
1
2
3
4
|
(initials '(if i needed someone))
(I I N S)
|
Answer:
1
2
3
4
5
|
(define (initials sent)
(if (empty? sent) '()
(se (first (first sent))
(initials (bf sent)))))
|
✅ Exercise 11.6
Write a procedure countdown that works like this:
1
2
3
|
(countdown 10)
(10 9 8 7 6 5 4 3 2 1 BLASTOFF!)
|
1
2
3
|
(countdown 3)
(3 2 1 BLASTOFF!)
|
answer:
1
2
3
4
|
(define (countdown num)
(if (= num 0) 'BLASTOFF!
(se num (countdown (- num 1)))))
|
I noticed that AnneB thought to handle the case where the number used is not a positive integer, so I wrote my own way to address that before reading hers:
1
2
3
4
5
6
7
8
9
|
(define (valid-num? num)
(and (integer? num)(>= num 0)))
(define (countdown num)
(if (not (valid-num? num)) 'ack!
(if (= num 0) 'BLASTOFF!
(se num (countdown (- num 1))))))
|
✅ Exercise 11.7
Write a procedure
copies
that takes a number and a word as arguments and returns a sentence containing that many copies of the given word:
1
2
3
|
> (copies 8 'spam)
(SPAM SPAM SPAM SPAM SPAM SPAM SPAM SPAM)
|
answer:
1
2
3
4
|
(define (copies num wd)
(if (= num 0) '()
(se wd (copies (- num 1) wd))))
|
After adding the check for valid numbers in the last exercise, I realized maybe I should do the same here:
1
2
3
4
5
6
7
8
9
|
(define (valid-num? num)
(and (integer? num)(>= num 0)))
(define (copies num wd)
(if (not (valid-num? num)) '(valid non-negative integers only, friend!)
(if (= num 0) '()
(se wd (copies (- num 1) wd)))))
|
Results:
1
2
3
4
5
6
7
8
9
10
|
(copies 8 'spam)
'(SPAM SPAM SPAM SPAM SPAM SPAM SPAM SPAM)
(copies 0.5 'spam)
'(valid non-negative integers only, friend!)
(copies 0 'spam)
'()
|