Fundamentals of Computer Science 1 (CS151 2003S)
[Skip to Body]
Primary:
[Front Door]
[Current]
[Glance]
-
[EC]
[Honesty]
[Instructions]
[Links]
[Search]
[Syllabus]
Groupings:
[EBoards]
[Examples]
[Exams]
[Handouts]
[Homework]
[Labs]
[Lab Writeups]
[Outlines]
[Project]
[Readings]
[Reference]
ECA:
[About]
[Grades]
[Quizzes]
[Submit Work]
[Change Password]
[Reset Password]
Misc:
[Scheme Reference]
[Scheme Report]
[CS151 2003S Gum]
[CS151 2002F]
[CS151 History]
[SamR]
Contents
A number of you seem inclined to make some things much more complicated than they are. For example, I saw each of the following expressions in the exams that I graded:
(+ 1)
(+ 0 n)
(string-append "")
(string-append "" "()")
The following would be much more sensible (primarily because people don't have to wonder why you're applying meaningless operations)
1
n
""
"()"
Some of you still fail to write the six P's in an appropriate format, to choose good variable names, and to make sure that your lines are short enough. You lost points on this exam. You will lose many more on the next exam.
Repeat after me:
if
cond
#t
or #f
, I
know I'm doing something wrong and I'll fix it before turning it
in to Sam.
Key Topics: Numeric Recursion
Document, write, and test a Scheme procedure, (count-appearances
digit val)
, that counts how many times the one-digit
integer digit appears in the non-negative integer value val.
For example,
> (count-appearances 1 12) 1 > (count-appearances 1 312) 1 > (count-appearances 1 10312101) 4 > (count-appearances 0 10312101) 2 > (count-appearances 5 10312101) 0 > (count-appearances 0 000100) 2
This problem requires that we think about two aspects: How we look at digits of a number and what we'll recurse over. We'll repeatedly look at the last digit in the number and remove that digit.
How do we get the last digit of a number? We look at the remainder after dividing the number by 10. How do we shrink the number? We divide by 10. When do we stop? When the number equals 0.
Are there any special cases? Certainly. If the digit is 0 and the number is 0, we'll return 1. In order to distinguish that case from recursing until we reach 0, we'll build a kernel for the recursion. As a lover of named lets, I'm going to use one for the kernel.
;;; Procedure: ;;; count-digits ;;; Parameters: ;;; digit, an integer ;;; num, a non-negative integer ;;; Purpose: ;;; Counts the number of times digit appears in num. ;;; Produces: ;;; count, a count of the number of appearances. ;;; Preconditions: ;;; digit is a single-digit number (0, 1, 2, ..., 9). ;;; num has the form d_n d_(n-1) d_(n-2) ... 3 2 1 ;;; Postconditions ;;; count is the number of d_i such that d_i = digit. (define count-digits (lambda (digit num) (if (and (zero? digit) (zero? num)) 1 (let kernel ((num num)) (cond ((zero? num) 0) ((= digit (remainder num 10)) (+ 1 (kernel (quotient num 10)))) (else (kernel (quotient num 10))))))))
For those not happy to think about the mathematical side of things, it is also possible to convert the problem to that of tallying. In particular, we can convert the number to a string and then the string to a list of characters. Here's an attempt to do so.
(define count-digits-again (lambda (digit num) (let ((digit-char (car (string->list (number->string digit))))) (let kernel ((chars (string->list (number->string num)))) (cond ((null? chars) 0) ((char=? digit-char (car chars)) (+ 1 (kernel (cdr chars)))) (else (kernel (cdr chars))))))))
We've eliminated the special case, but at the cost of less elegance and some less understandable code.
I intended this to be a relatively straightforward question. In particular, the lab on numeric recursion included an example procedure that counted the number of digits in an integer. I expected that you'd be able to use a similar technique for this problem.
Key Topics: List Recursion, Deep Recursion, Equality Testing, Predicates
Write and test a procedure,
(in-tree? value tree-of-values)
,
which determines whether the simple value value is either equal to
tree-of-values or appears somewhere
in tree-of-values, where tree-of-values is
built by combining a number of cons cells (either explicitly,
with cons
or implicitly, with list
).
You need not document this procedure.
> (in-tree? 0 null) #f > (in-tree? 0 0) #t > (in-tree? 0 1) #f > (in-tree? 0 (cons 1 1)) #f > (in-tree? 0 (cons 0 1)) #t > (in-tree? 0 (cons 1 0)) #t > (in-tree? 0 (cons 1 (cons 0 2))) #t > (in-tree? 0 (list 0)) #t > (in-tree? 0 (list 5 4 3 2 1 0)) #t > (in-tree? 0 (list (list "one" 2 'three) (list 0) (list 2))) #t
Recall that trees are defined recursively: A tree is either (1) a non-pair value or (2) a pair of trees. Hence, our solution will be defined recursively. The base case is the non-pair value. We can determine if a value is in the simplest tree by comparing the two values. A value is in a pair of trees if it is in either tree.
Translating that into Scheme code:
;;; Procedure: ;;; in-tree? ;;; Parameters: ;;; val, a simple value ;;; tree, a tree of values ;;; Purpose: ;;; Determine if val appears in tree. ;;; Produces: ;;; is-in-tree, a truth value. ;;; Preconditions: ;;; [The Types Match] ;;; Postconditions: ;;; is-in-tree is true (#t) if either (1) tree is a singleton value ;;; and val equals tree or (2) tree is a pair and val is in one of ;;; the subtrees. (define in-tree? (lambda (val tree) (if (pair? tree) (or (in-tree? val (car tree)) (in-tree? val (cdr tree))) (equal? val tree))))
Steven and Sarah Stringer find it fascinating that Scheme can figure out how to print such a wide variety of Scheme types. They find it particularly interesting that Scheme is able to print out lists without knowing their length in advance. They've asked you to show them some code that explains what Scheme does.
Write a procedure, (list-of-integers->string lst)
,
which takes a list of integers and converts it to the corresponding string.
For example,
> (list-of-integers->string null) "()" > (list-of-integers->string (cons 1 null)) "(1)" > (list-of-integers->string (list 1 2)) "(1 2)" > (list-of-integers->string (cons 1 (cons 2 (list 5 4 3)))) "(1 2 5 4 3)"
You will probably find it useful to use the built-in
number->string
procedure. I expect that you will
also want to create a helper procedure to deal with all but the first
element of the list.
If you are feeling particularly ambitious (that is, this part of the problem is optional), you can handle nested lists and things built of cons cells that aren't necessarily lists.
We need to print an open paren, the values separated by spaces, and the close paren. We'll use a kernel to print the values. The special cases to handle are the empty list and the singleton list (the two times we shouldn't include spaces).
;;; Procedure: ;;; list-of-integer->string ;;; Parameters: ;;; lst, a list of integers ;;; Purpose: ;;; Convert a list of integers to a string. ;;; Produces: ;;; str, a string ;;; Preconditions: ;;; [None] ;;; Postconditions: ;;; str is the string Scheme would print for lst. (define list-of-integers->string (lambda (lst) (letrec ( ; Convert a list of numbers to a string with the ; numbers separated by spaces. Doesn't include parens. (kernel (lambda (lst) (cond ((null? lst) "") ((null? (cdr lst)) (number->string (car lst))) (else (string-append (number->string (car lst)) " " (kernel (cdr lst))))))) ) (string-append "(" (kernel lst) ")"))))
An and Al Abbrev object to the overly-long procedure names that
Sam likes to use, like
.
Hence, they tend to choose one-character
names. They also avoid the six P's that I like. Here's a procedure
they've recently written.
list-of-integers->string
(define r (lambda (l p?) (letrec ((c (lambda (p v) (let ((x (if (p? v) 1 0))) (cons (+ (car p) x) (+ (cdr p) (- 1 x)))))) (r (lambda (q m) (if (null? m) (/ (car q) (cdr q)) (r (c q (car m)) (cdr m)))))) (r (cons 0 0) l))))
a. Change the various names in the procedure to clarify what the procedure does.
b. Add internal comments to explain the various parts.
c. Add introductory comments (the six P's) to explain the purpose (and the other P's) of the procedure.
What do we observe?
m
is another name for l
(since
l
is passed as the second parameter to the internal
r
.
r
recurses, cdr
ing through the list
at every step.
p?
is a predicate (given the name).
c
is called with q
and the car
of m
(that is, l
).
c
is called for every element of l
.
c
do? It checks if p?
holds on
each value. If p?
holds, x
is 1 and
(- 1 x)
is 0. Otherwise, x
is 0 and
(- 1 x)
is 1.
car
and cdr
of p
.
p?
holds, we increment the car
.
Otherwise, we increment the cdr
.
car
is the number
of times the predicate held and the cdr
is the number
of times the predicate failed to hold.
;;; Procedure: ;;; ratio ;;; Parameters: ;;; pred?, a unary predicate ;;; lst, a list of values ;;; Purpose: ;;; Compute the ratio of values for which pred? holds to values ;;; for which pred? fails to hold. ;;; Produces: ;;; rat ;;; Preconditions: ;;; pred? can be applied to every element of lst. ;;; pred? fails to hold for at least one element of lst. ;;; Postconditions: ;;; rat = holds/notholds where holds is the number of values in ;;; lst for which (pred? val) is not false and notholds is ;;; the number of values in lst for which (pred? val) is false. ;;; rat is presented in simplest form. For example, if pred? holds ;;; for two values and fails to hold for four values, rat is ;;; 1/2 rather than 2/4. (define ratio (lambda (lst pred?) (letrec ((count-one (lambda (holds-notholds val) (let ((this-holds (if (pred? val) 1 0))) (cons (+ (car holds-notholds) this-holds) (+ (cdr holds-notholds) (- 1 this-holds)))))) (kernel (lambda (holds-notholds remaining) (if (null? remaining) (/ (car holds-notholds) (cdr holds-notholds)) (kernel (count-one holds-notholds (car remaining)) (cdr remaining)))))) (kernel (cons 0 0) lst))))
Although I did not take off, I was hoping that you would include the following two key preconditions:
p?
is a unary predicate;
p?
can be applied to each element of l
.
Wednesday, 9 April 2003 [Samuel A. Rebelsky]
Thursday, 10 April 2003 [Samuel A. Rebelsky]
Friday, 11 April 2003 [Samuel A. Rebelsky]
[Skip to Body]
Primary:
[Front Door]
[Current]
[Glance]
-
[EC]
[Honesty]
[Instructions]
[Links]
[Search]
[Syllabus]
Groupings:
[EBoards]
[Examples]
[Exams]
[Handouts]
[Homework]
[Labs]
[Lab Writeups]
[Outlines]
[Project]
[Readings]
[Reference]
ECA:
[About]
[Grades]
[Quizzes]
[Submit Work]
[Change Password]
[Reset Password]
Misc:
[Scheme Reference]
[Scheme Report]
[CS151 2003S Gum]
[CS151 2002F]
[CS151 History]
[SamR]
Disclaimer:
I usually create these pages on the fly
, which means that I rarely
proofread them and they may contain bad grammar and incorrect details.
It also means that I tend to update them regularly (see the history for
more details). Feel free to contact me with any suggestions for changes.
This document was generated by
Siteweaver on Tue May 6 09:22:21 2003.
The source to the document was last modified on Fri Apr 11 13:08:06 2003.
This document may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2003S/Exams/notes.02.html
.
;
;
Check with Bobby