Review: write the following:
;; path-to-mona: FamTree --> list-of-symbol, or false
;; Return a series of names leading from a-ft back
;; to an ancester named 'mona, or false if no such ancester.
;; (If multiple ancestors with that name,
;; a path to any is okay.)
;;
(define (path-to-mona a-ft)
   (cond [(unknown? a-ft)   ...]
         [(child?   a-ft)   ..(child-birthyear a-ft)..
                            ..             (child-name a-ft) ..

                            ..   (path-to-mona (child-ma a-ft))..
                            ..   (path-to-mona (child-ma a-ft))..

                            ..   (path-to-mona (child-pa a-ft))..
                            ..   (path-to-mona (child-pa a-ft))..
                                                                 ]))

(path-to-mona marge-tree) = false
(path-to-mona bart-tree) = (cons 'bart (cons 'home (cons 'mona empty)))

Sometimes, we write programs that take two inputs with complicated data definitions, such as two lists. Depending on the program we're trying to write, we may or may not need to use a more complicated template. Let's explore this by considering several examples.

  1. append:
    ;; append : list-of-nums list-of-nums -> list-of-nums
    ;; produces a list with all elements of the first followed by those of
    ;; the second, in order
    (define (append lon1 lon2)
      (cond [(empty? lon1) ...]
    	[(cons?  lon1) ..... (first lon1)
                                  ...                      ]))
    
    In this example, we don't need to look inside lon2, we just need to use it at an appropriate time. Using the template on lon1 only therefore suffices.
  2. make-points
    ;; make-points : list-of-nums list-of-nums -> list-of-posn given two
    ;; lists of the same length, returns a list of points formed pairwise
    ;; from the two input lists
    (define (make-points x-coords y-coords) ...)
    
    Here, we will need to traverse the y-coords. However, note that the two input lists are assumed to have the same length. With this knowledge, what might we use for a template?
    (define (make-points x-coords y-coords)
      (cond [(empty? x-coords) ...]
    	[else ... (first x-coords) ... (first y-coords)
    	      ... (make-points (rest x-coords) (rest y-coords)) ...]))
    
    Why does this template make sense? Since the lists must have the same length, either both lists are empty or neither list is empty. So the template needs only two cases. Furthermore, since the rests of the lists must have the same length, it makes sense to call make-points recursively on the rests of the lists. The final function looks like: (define (make-points x-coords y-coords) (cond [(empty? x-coords) empty] [(cons? x-coords) .. (first x-coords) .. (first y-coords) .. (make-points (rest x-coords) (rest y-coords))..]))
  3. merge
    ; merge : list-of-numbers list-of-numbers -> list-of-numbers
    ; merges two lists (sorted in *ascending* order) into one list (sorted
    ; in ascending order)
    (define (merge alon1 alon2) ...)
    

    Here, we cannot assume anything about the lengths of the two lists, and we need to look at all elements of both lists. Therefore, neither of our two previous cases apply.

    Let's start by making up examples of this function. How many examples do we need at a minimum? Two unrelated inputs, each with two possible cases in their data definitions means at least 4 examples.

    (merge empty empty) = ..
    (merge empty (cons 1 (cons 5 empty))) = ..
    (merge (cons 1 (cons 5 empty)) empty) = ..
    (merge (cons 1 (cons 5 empty)) (cons 6 (cons 7 empty))) = ..
    (merge (cons 1 (cons 6 empty)) (cons 5 (cons 7 empty))) = ..
    (merge (cons 5 (cons 6 empty)) (cons 1 (cons 7 empty))) = ..
    
    These examples show that our program must be able to handle all four cases. Thus, our template will need a cond with four cases. What questions distinguish these cases? Let's use a table to work them out.
    	         (empty? alon2)	      (cons? alon2)
    -----------------------------------------------------------
    (empty? alon1)  (and (empty? alon1)   (and (empty? alon1)
                         (empty? alon2))       (cons? alon2))
    
    (cons? alon1)   (and (cons? alon1)    (and (cons? alon1)
                         (empty? alon2))       (cons? alon2))
    
    With the table, we can now write a general template for two lists. Ignoring the recursion, we have
    (define (f alon1 alon2)
      (cond [(and (empty? alon1) (empty? alon2))
             ...]
    	[(and (empty? alon1) (cons? alon2))
    	 ... (first alon2) ... (rest alon2) ...]
    	 [(and (cons? alon1) (empty? alon2))
    	 ... (first alon1) ... (rest alon1) ...]
    	[(and (cons? alon1) (cons? alon2)) 
    	 ... (first alon1) ... (first alon2)
             ... (rest alon1)  ... (rest alon2) ...]))
    
    What about the recursions? In the first case there's nothing to recur on, and in the second and third cases there's only one non-empty list to recur on. In the last case, we have several options:
    1. - (f alon1 (rest alon2))
    2. - (f (rest alon1) alon2)
    3. - (f (rest alon1) (rest alon2))
    There are problems that would use each of these. Since we don't know which one we need, we show all three in the template:
    (define (f alon1 alon2)
      (cond [(and (empty? alon1) (empty? alon2))
             ...]
    	[(and (empty? alon1) (cons? alon2))
    	 ... (first alon2)
             ... (f alon1 (rest alon2)) ...]
    	[(and (cons? alon1) (empty? alon2))
    	 ... (first alon1)
             ... (f (rest alon1) alon2) ...]
    	[(and (cons? alon1) (cons? alon2)) 
    	 ... (first alon1) ... (first alon2)
             ... (f alon1 (rest alon2))
             ... (f (rest alon1) alon2)
             ... (f alon1 alon2) ...]))
    
    Now let's finish merge:
    (define (merge alon1 alon2)
      (cond [(and (empty? alon1) (empty? alon2))
             empty]
    	[(and (empty? alon1) (cons? alon2))
    	 alon2]
    	[(and (cons? alon1) (empty? alon2))
    	 alon1]
    	[(and (cons? alon1) (cons? alon2))
             (cond [(< (first alon1) (first alon2))
                    (cons (first alon1) (merge (rest alon1) alon2))]
                   [else
                    (cons (first alon2) (merge alon1 (rest alon2)))])]))
    
    This could be shortened by combining the first and (second or third) cases. Doing so is not necessarily a simplification, because it obscures the structure. On such examples, when writing the template, you should provide this long version, and optionally a shortened version.

These three programs demonstrate three different kinds of programs that process two complex inputs:

  1. one complex input need not be traversed entirely
  2. both inputs must be traversed, both of the same length
  3. (the general case:) both inputs must be traversed, different lengths


For practice distinguishing between and writing these kinds of programs, try the following exercises. Those with a star(*) are of the caliber that could be asked on exam1. (The exam will not cover problems that must process two complex arguments (case 3), though there might be a problem which accepts two complex arguments and only recurs on one (case 1), or recurs on both in lock-step (case 2).)