;; 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.
;; 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.
;; 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))..]))
; 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:
(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:
;; list-ref1 : list-of-symbols N[>=1] -> symbol or false return the ;; nth symbol in the list (counting from 1) or false if there is no ;; nth itemWhich kind is this?
(list-ref1 empty 1) = false (list-ref1 (cons 'hi empty) 1) = 'hi (list-ref1 empty 5) = false (list-ref1 (cons 'hi empty) 2) = false (define (list-ref1 alos n) (cond [(and (= n 1) (empty? alos)) ..] [(and (> n 1) (empty? alos)) ..] [(and (= n 1) (cons? alos)) ..] [(and (> n 1) (cons? alos)) ..]))Once again, we could combine the first and second cases. N.B. there is a built-in list-ref, which would better be called list-ref0, since it starts counting from 0.
;; avg-2-lists: list-of-number, list-of-number --> list-of-number ;; Given two lists of the same length, return a list that has the average ;; of corresponding entries.