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.

  1. 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).

  2. 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).

  3. Exercise 10.6.b

  4. 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:

    1. (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)
    2. (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.

  5. 10.7b (your function should be named filter)

  6. 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:

    1. The empty list is an empty tree
    2. A list with a single element is a tree with a single node (i.e., a leaf)
    3. The first element of a list is the root of the tree. The remaining elements are sub-trees.
    For example the tree:
          |    |    |
          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)
          ((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))))

    1. 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.

    2. 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.

    3. 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.

  7. Based on the definition of a tree from the previous problem, write a function named fringe that takes a list and returns an ordered 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.

  8. 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:

    1. 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.

    2. 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.

  9. 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 23.

What to Submit

  1. The answers to questions 6 should be placed in an ascii text file or a pdf file.
  2. 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.
  3. Your SAIS confirmation page if you choose to complete it. You must bring a print out of it to class.