Answer four of the following six questions. Each question is worth 25 points. While the problems have equal value, they are not of equal difficulty. Some will take you longer, some will take you less time.
1. Testing Scheme Routines
Suppose you've been hired to test a Scheme function which is documented as
;;; (positionOf val lst)
;;; Given a number, val, and a sorted list of numbers,
;;; lst, determine the position of val in lst.
;;; If the value is in the list, returns the position of the
;;; first appearance of the value.
;;; If the value is not in the list, returns 0.
;;; The list must be sorted in increasing order.
;;; All elements of the list must be numbers.
;;; The value must be a number.
;;; The appropriate position is returned (as described above).
;;; The list is not modified.
;;; (positionOf 1 '(1 5 11)) => 1
;;; (positionOf 2 '(1 1 2 3)) => 3
;;; (positionOf 3 '(2 3 3 3) => 2
;;; (positionOf 2 '(1 5 11)) => 0
;;; (positionOf 1 '()) => 0
;;; (positionOf 1 '(3 2 1)) => unspecified, may crash
;;; (positionOf 1 '(a 1 b)) => unspecified, may crash
;;; (positionOf 1 '(1 a b)) => unspecified, may crash
You may assume that the people who wrote
working in good faith. That is, they tried to create a working
function, but may have failed nonetheless because of inadvertent errors.
Write a Scheme predicate,
testPositionOf?, that performs a
reasonable set of tests on the
positionOf function and
returns true if it appears that the function works correctly and false
otherwise. Your predicate should not return true if it is likely that
positionOf fails to work as advertised nor should it return
false if it is likely that
positionOf is likely to work
correctly. Your goal is only to check that it correctly identifies the
position of a value. You need not the other postconditions.
You may rely on the existence of "reasonable" helper functions (such as
nints). Just make sure to document clearly what you expect
the helper functions to do.
Since you are working under time pressure, I will not expect a perfectly thorough or working answer, simply a reasonable one.
Everyone who took CS223 (which should be most of you) should have known this answer, since it's a key part of a chapter of Bentley that we discussed (remember that chapter on binary search, testing binary search, and loop invariants). However, few of you seemed to recall it. Those who didn't recall it should still have been able to model their tests on the one from assignment five. In particular, it is important to test different size lists. In addition, it is important to test the case that the element isn't in the list as well as the case that it is in the list. For "not in the list", you should not just check "smaller than the front" and "larger than the back".
For these purposes, it seems sufficient to test list sizes from 0 to some reasonable number (perhaps 8, perhaps sixteen) and to test a search for each position in the list and between each two positions in the list (as well as before the beginning and after the end).
To make it easy to check whether the search is successful, I'll build a somewhat regular list. In particular, I'll build a list of even integers starting with two. That way, an even number is at position N/2 and odd numbers aren't in the list. To handle the different length lists, I'll use a helper function. The disadvantage of this solution is that it fails for a function that simply returns N/2 for even numbers and 0 for odd numbers. However, we could use a couple of variants to handle such cases. Since we're assuming that the people who gave us the function are working in good faith, such cases may not be necessary.
First, a helper function that checks whether the function returns the correct position when looking for a value in the list.
;;; Check if a positionOf function finds the correct position of ;;; a number in a list of even numbers starting with two. If the ;;; number is odd, it shouldn't be in the list. If the number is ;;; even and the length of the list is less than or equal to N/2, ;;; then it should be at position N/2. (define (testParticularPosition fun num lst) (if (odd? num) (equal? 0 (fun num lst)) (equal? (/ num 2) (fun num lst))))
Next, a helper function that checks every position in the list.
;;; Check if a positionOf function finds the correct position of ;;; every number between 1 and 2N+1 in the list (2 ... 2N). (define (testAllPositions fun N) (let ((lst (evens N))) (myand (map (lambda (num) (testParticularPosition fun num lst)) (nints (+ (* 2 N) 1) 1)))))
Finally, our test function. It checks a variety of sizes of lists. Note
that we use
map on a list of integers to test the different
size lists and then we use "and" the results together.
;;; Check if a positionOf function finds the correct position of ;;; every number between 1 and 2N+1 in every list size between ;;; 0 and 16. (define (testPositionOf? fun) (myand (map (lambda (len) (testAllPositions fun len)) (nints 17 0))))
In order for this to work correctly, we need the following functions defined.
;;; (nints n start): ;;; create the list of n consecutive integers starting with start ;;; (evens n): ;;; create the list of n consecutive even numbers starting with 2 ;;; (myand lst): ;;; "and" all the elements of a list of booleans
The definitions of all three are relatively straightforward.
;;; Create a list of n consecutive integers starting with start (define (nints n start) (if (= n 0) nil (cons start (nints (- n 1) (+ start 1))))) ;;; Create a list of n consecutive even integers starting with 2. ;;; If n is odd, creates a list of n consecutive odd integers. (define (evens n) (letrec ((evens-from (lambda (n start) (if (= n 0) nil (cons start (evens-from (- n 1) (+ start 2))))))) (evens-from n 2))) ;;; "And" all the elements of a list of booleans. If the list is ;;; empty, return true (#t). (define (myand lst) (if (null? lst) #t (and (car lst) (myand (cdr lst)
Note that this isn't an ideal solution, as it doesn't check lists with duplicates. We might instead build a list of pairs, such as
Then the appropriate place for each element is the value of the element.(1 1 3 3 5 5 7 7 ...)
For those interested in testing their own solutions, here's a
positionOf function to test. Note that I've used
ifs when a
cond would probably
be somewhat better.
;;; Find the position of a number in a sorted list. Still a linear ;;; time method, but stops when it goes too far. (define (positionOf num lst) ; Base case: empty list. The number can't be there. (if (null? lst) 0 ; Base case: number is at the start of the list. Return 1. (if (= num (car lst)) 1 ; Base case: number is too small. Return 0 (if (< num (car lst)) 0 ; Recursive case: look for the number in the rest of the list (let ((check (positionOf num (cdr lst)))) ; If it's not in the rest of the list, give up (if (= check 0) 0 ; If it is in position X in the rest of the list, it's at ; position X+1 in the whole list (+ check 1)))))))
Here's one to test that has a slight bug.
(define (badPositionOf num lst) ; Base case: empty list. The number can't be there. (if (null? lst) 0 ; Base case: single element list and the number is too small. It ; can't be there. (if (and (null? (cdr lst)) (<= num (car lst))) 0 ; Base case: number is at the start of the list. Return 1. (if (= num (car lst)) 1 ; Base case: number is too small. Return 0 (if (< num (car lst)) 0 ; Recursive case: look for the number in the rest of the list (let ((check (badPositionOf num (cdr lst)))) ; If it's not in the rest of the list, give up (if (= check 0) 0 ; If it is in position X in the rest of the list, it's at ; position X+1 in the whole list (+ check 1))))))))
2. Removing Copies
Often, it is useful to remove all copies of a value from a list of
values. Scheme includes such a function as one of its basic functions.
Nonetheless, I would like you to write such a function for yourself.
Write a function
(removeAllCopies val lst) that returns a
lst with all elements equal to
removed. Note that some or all values in the list may not be numbers.
Everyone who did this problem did a fairly good job. Some seemed to
think that they needed to cons nil with the rest of the list in the
"it's at the front" case, but that's clearly wrong. Some used
= instead of
equal?, but I didn't consider
that a real problem.
;;; Remove all copies of a value from a list of values. Written ;;; to handle arbitrary kinds of values. (define (removeAllCopies val lst) (if (null? lst) nil (let ((first (car lst)) (rest (removeAllCopies val (cdr lst)))) (if (equal? first val) rest (cons first rest)))))
3. Triangular Numbers
Without using recursion or loops, write a Haskell function,
triangular that returns the series of triangular numbers,
1 3 6 10 15 21 ...
You may use lambda abstraction,
and other similar functions.
Make sure you indicate the type of your function.
Many of use missed the fact that
triangular produces an
infinite list. In effect,
triangular takes no parameters.
Its type is therefore "list of integers" which we represent as
triangular :: [Int]
Since I disallowed recursion, the most reasonable thing to do was to simply map a function that computes the nth triangular number onto the list of integers. Here's that solution. Note that to compute the nth triangular number, you simply sum the numbers from 1 to n.
triangular = map (\n -> sum (take n (intsfrom 1))) (intsfrom 1)
There is also an elegant recursive solution that involves using the previous result (since the nth triangular number is the (n-1)st triangular number + n).
triangular = 1 : (sumpairs triangular (intsfrom 2))
This requires a helper function to add pairs of numbers, one from each list.
sumpairs :: [Int] -> [Int] -> [Int] sumpairs (x:xs) (y:ys) = (x + y) : (sumpairs xs ys)
4. Binary Trees
Define a general binary tree datatype in Haskell, including appropriate type constructors. Then write a function that converts binary trees to lists. That is, your function should take a binary tree as input and create a list of the elements in the tree. You can choose the traversal order for the conversion function.
Note that an answer to this could be found in the Gentle Introduction to Haskell that I recommended you read. This is a variant.
I've chosen to have leaves that include no values, so the only values are in the interior of the tree. This makes my life a little bit easier.
data BinaryTree a = Leaf | Node a (BinaryTree a) (BinaryTree a)
For traveral, I'll take advantage of recursion and do a depth-first,
preorder, left-to-right traveral. (Many of you neglected to describe
which traversal order you were using.) Note that
++ is the
Haskell concatenation operator.
treeToList :: (BinaryTree a) -> [a] treeToList Leaf = () treeToList (Node val left right) = [val] ++ (treeToList left) ++ (treeToList right)
5. Functional Programming in Pascal
In the start of chapter 10, Louden suggests that it's possible to do "functional" programming in Pascal. However, much of what he calls "functional" is simply recursive. Could one convince a functional programmer trained in Scheme or Haskell that Pascal is functional? If so, how? If not, why not?
There are a number of aspects to functional programming. While the
application of functions is certainly important, the use of functions as
"first class values" is also important. While some version of Pascal do
permit the use of functions as parameters to other functions, Pascal
does not permit you to return functions from functions. So, for
example, it would be impossible to write
compose in Pascal.
Some of you noted that Pascal relies on side effects for much of its computation. Nonetheless, it is certainly possible to write a Pascal program without side effects (as long as you're willing to do without assignment). In addition, there are many functional languages (Scheme included) that permit side effects.
Pascal also lacks facilities to return structured types, does not include garbage collection or symbolic values, and does not permit lambda abstractions.
Note that p. 365 of Louden contains many similar arguments.
6. Evaluation and Application
apply functions that
make it possible for programmers to build and evaluate expressions. For
> (define exp '(+ 2 3))
(+ 2 3)
Error: attempt to apply non-procedure (+ 2 3)
> (eval exp)
> (define nums '(2 3 1 5))
(2 3 1 5)
> (+ nums) ; remember that + is "sum"
Error in +: (2 3 1 5) is not a number.
> (apply + nums)
> (define (oddlist a b) (list a b b a))
> (oddlist 'alpha 'beta)
(alpha beta beta alpha)
> (define ab '(alpha beta))
> (define exp2 '(oddlist ab))
> (eval exp2)
Error: incorrect number of arguments to #<procedure oddlist>.
> (define exp3 '(apply oddlist ab))
(apply oddlist ab)
> (eval exp3)
(alpha beta beta alpha)
Haskell appears to include no explicit equivalent to either
apply. This suggest either that such
functions are unnecessary in Haskell or that it would be impossible to
include them in Haskell. Which do you think is the case, and why?
Because Haskell is curried, we don't need
simply writing a function and its arguments gives us application (we
apply in Scheme because we have functions that we
want to apply to multiple parameters). For example, to apply
val2, we could
fun val1 val2. However, if
val2 are in a list, we need to do something a little
bit more sophisticated (but certainly possible).
It would be difficult if not impossible to write
eval cannot be typed. What kind of value does it
return? You can't just say "some type". For example, is
whatever) + 2 a legal expression? Similarly, it's not clear what
the argument type for
eval is. Presumably, it's something
Expression, but what are expressions?
Many of you said "Haskell is lazy; neither fits in the context of lazy
evaluation." That is not a clear argument nor does it seem correct.
eval exp is simply an expression whose result
is the result of evaluating exp. If the "eval expression" is
not needed, then the evaluation never needs to be done.
A number of you suggested that the role of lists is quite different
in the two languages and that
eval corresponded to Scheme's
notion of list and not Haskells. I would tend to agree with that
Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.
Source text last modified Wed Apr 29 10:12:05 1998.
This page generated on Wed Apr 29 10:16:54 1998 by SiteWeaver.
Contact our webmaster at firstname.lastname@example.org