Homework Assignment 10
In this assignment you cannot use any imperative constructs of scheme, such
as set! or the iterative loop constructs. You also are not allowed to use
higher level functions, such as length to get the length of the list. Instead you must
use the basic built-in functions for manipulating a list--car, cdr, cons, append--and
the built-in arithmetic operators to create your functions.
- Write a function in scheme called swap that takes two
arguments and returns a cons pair, with the smaller argument first
and the larger argument second. For example, the call:
(swap 9 6)
should return the cons pair (6 . 9).
- Write a recursive function in scheme named avg
that takes a list of numbers and returns
their average as a floating point number. For example, the call:
(avg '(3 6 17 12 15))
should return 10.6 (and not 53/5 which is what you'll get if you do not
ensure that you have a floating point operand in one of the dividend or
divisor).
- Exercise 10.6.b
- Write a recursive function named mergesort that takes a single list
argument and returns the list in sorted order. For example:
(mergesort '(1 3 2 4 8 1 9 6 10)) ==> '(1 1 2 3 4 6 8 9 10)
As long as you use the purely functional features of scheme (i.e., no
imperative constructs), you may design your merge sort function any way
you see fit. However, it helped me to define the following two helper
functions, which
I created and tested first, and then used them to build mergesort:
- (merge L1 L2): takes two lists and returns a single merged list.
For example,
(merge '(2 4 6) '(1 3 5 9 10)) ==> '(1 2 3 4 5 6 9 10)
- (mergesortHelper L L1 L2 whichlist?): divides a list L into
two separate, equal-sized lists L1 and L2. whichlist? indicates
which list the next element of L should be added to. I used cons
to add the next element of L to either L1 or L2. mergesortHelper
should be recursive and should use continuation-style arguments
for L1 and L2 (i.e., L1 and L2 grow with each successive call
to mergesortHelper). mergesortHelper can either return L1 and L2 as
a cons pair when L is empty, or it can directly implement the
general case of merge sort once L is empty (i.e., call mergesort
on each of the two lists L1 and L2 and call merge to merge the
resulting two lists).
My eventual mergesort was very short. It implemented the two base cases
where the list is either empty or has one element, in which case it
simply returns the list, and it implemented the general case by calling
mergesortHelper with the appropriate initial arguments.
- 10.7b (your function should be named filter)
- Consider the problem of determining whether two trees have the same
set of nodes if both trees have their nodes enumerated by a pre-order
traversal. You should assume that a tree is constructed as follows:
- The empty list is an empty tree
- A list with a single element is a tree with a single node (i.e.,
a leaf)
- The first element of a list is the root of the tree. The remaining
elements are sub-trees.
For example the tree:
6
|
-----------
| | |
3 5 8
/ \ |
11 9 ----------
| | | |
1 20 4 17
/ \
7 10
would be represented as the list:
'(6 (3 (11) (9)) (5) (8 (1) (20 (7) (10)) (4) (17)))
An obvious way to solve the problem is to 1) write a function that
performs a pre-order traversal of each tree and returns a list of
the nodes in each tree, and then 2) compare the two lists. Here
is some sample code that would accomplish these two tasks:
(define preorder
(lambda (L)
(cond
((null? L) L) ; base case that stops the recursion--returns a null list
; processing a list of sub-trees? If so append the nodes that
; result from a preorder search of the first subtree with the
; nodes that result from a preorder search of the remaining subtrees
((list? (car L)) (append (preorder (car L)) (preorder (cdr L))))
; else we're processing the root of the tree. add the root of the tree
; to the nodes that result from a preorder search of its subtrees
(else (cons (car L) (preorder (cdr L)))))))
(define same-nodes
(lambda (T1 T2)
(equal (preorder T1) (preorder T2))))
- To see what is happening in a functional program, it can help to
trace the execution of the program. Trace the execution of the following
call of preorder. Assume that arguments are evaluated left-to-right.
You may be tempted to write a program to perform
the following trace, but I suggest you try to do it by hand, both
because I think
you will gain a better understanding of what is happening and because
the hydra version of scheme does not use left-to-right evaluation of
the arguments to preorder and you will quite possibly give me the wrong
answer.
(preorder '(6 (5) (8 (1) (20 (7) (10)))))
To show you what I want your trace execution to look like, here is a
sample trace execution:
(preorder '(3 (11) (9)))
(cons 3 (preorder '((11) (9))))
(preorder '((11) (9)))
(append (preorder '(11)) (preorder '((9))))
; note that '((9)) is not a typo, it is the cdr of the list
(preorder '(11))
(cons 11 (preorder '())) ==> '(11)
; I do not need to see the trace execution of preorder on an empty list
(preorder '((9)))
(append (preorder '(9)) (preorder '()))
(preorder '(9))
(cons 9 (preorder '())) ==> '(9)
; result of (append (preorder '(9)) (preorder '()))
==> (append '(11) '(9))
==> '(11 9)
; result of (cons 3 (preorder '((11) (9))))
==> (cons 3 '(11 9))
==> '(3 11 9)
The trace execution shows which of the two branches of the conditional is
executed, then shows any non-trivial recursive executions of preorder
(trivial executions occur when preorder is called on an empty list), and
finally shows how the initial execution gets reduced to the final result
for that function call.
- How efficient is same-nodes when the trees differ in their first few leaves
(i.e., the two pre-order lists differ almost immediately)?
Give me the Big-O running time and justify the Big-O running time.
- How would your answer differ in a language like Haskell, which uses
lazy evaluation for all arguments? Give me the Big-O running time when
the trees differ in their first few leaves (e.g., the first 10 leaves) and
justify the Big-O running time.
- Based on the definition of a tree from the previous problem,
write a function named
fringe that takes a list and returns a list of
the leaves of the tree, in left-to-right order. For example,
(fringe '(6 (3 (11) (9)) (5) (8 (1) (20 (7) (10)) (4) (17))))
should return
'(11 9 5 1 7 10 4 17)
Hint: I found it very helpful to write a recursive definition of
fringe that use the map and fold functions from page 530 of the Scott
text. I would use the map function to apply fringe to the list of sub-trees
for a tree and then use fold to append the lists returned by each invocation
of fringe into a single list.
- In this problem you are going to create a "stream" that lazily generates
fibonacci numbers and you are going to create a function named printFib that
prints the first n elements of this list. To complete this problem, you
need to write two pieces of Scheme code:
- Define a "stream" named fib that lazily creates a list
of fibonacci numbers, assuming that f0 is 0 and f1 is 1. Use the
natural number example on page 276 of the book (example 6.84) as a
template. Note that you will need to use Scheme's delay
function. A few pages before that is a good example of a Scheme fib
function that you can modify.
- Define a function named printFib that takes two arguments,
the fib stream object you defined in part a and a number n
that represents the number of fibonacci numbers to print. Print these
fibonacci numbers. For example:
(printFib fib 10) ==> 0 1 1 2 3 5 8 13 21 34
My solution used the display function to print the numbers and
the let construct to ensure that the display functions were executed
in the correct order (i.e., the display function that prints a number
was executed before the display function that printed a space).
My let statement used an empty list for the name-value bindings. Note that
you will need to use Scheme's force function. Again look
at the natural number example on page 276 of the book (example 6.84) as
a template for using the force function.
- Extra Credit (30 points):
Do the online SAIS evaluation for me and any of the TAs
you wish to, and then either print a copy of the confirmation page and
give it to me at the beginning of class or else submit a pdf file with it
as part of your hw 10 submission.
I will be accepting printed copies through Thursday, April 28.
What to Submit
- The answers to questions 6 should be placed in an ascii text file
or a pdf file.
- The functions in the remaining questions should be placed in a file
named hw10.scm. We will load this file into the scheme read-eval-print
loop and call your functions with test data to check them.
- Your SAIS confirmation page if you choose to complete it.
You must bring a print out of it to class.