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

These are all variations on the theme of the design recipe, and using templates appropriately in various situations. One fourth variation on the template is: don't use it at all, becuase you are calling helper functions.

;; average: nonempty-list-of-nums --> number
;;
(define (average nelons)
  (/ (sum nelons) (length nelons)))
The template is being used inside the helpers, just not in the main function. (Aside: we required "nonempty-list-of-nums" as the type of data coming in. this is because average doesn't make sense for empty lists. You could make a generalized verion of average which takes in any list, and returns a number or 'average-not-sensical; this version would have a cond, but really that is not from the template/data type of input -- it's from the problem, which is including a divide-by-zero check.) We saw the data definition for non-empty lists in lab, though not in lecture.

;; list-ref1 : list-of-symbols, num (>= 1) --> symbol or false
;; Return the
;; nth symbol in the list (counting from 1), or false if there is no
;; nth item
Which kind is this?
  1. ? because there is only one list input? No, because you also need to recur on the structure of the natural number.
  2. ? because you want to recur on the list and number in lock-step? Almost, but we aren't assuming that they are of the same "length".
  3. ? It must be the general case.

(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))                    ..]))





(list-ref1 empty 1) = false
(list-ref1 (cons 'hi empty) 1) = 'hi
(list-ref1 empty 5) = false
(list-ref1 (cons 'hi empty) 2) = false
Once again, we could combine the first and second cases. Also, what is the problem of using false as a sentinel meaning entry-not-found? Nothing here, but if we had a list of symbol-or-boolean?

N.B. there is a built-in list-ref, which would better be called list-ref0, since it starts counting from 0. (In fact, given list-ref0 built-in, how would you instead write list-ref1, which just starts counting from 1 instead of 0? So this becomes an example of where using helper functions trumps the use of a template.)


Mutual Recursion: Descendent trees

minacious: of a menacing or threatining nature; minatory.
minatory: of a menacing or threatening nature; minacious.
sea water: see "water, sea".

Our current model of family trees makes it easy to find someone's person, but hard to find someone's children. Assume we reversed the arrows on the family tree from the last lecture. What sort of data definition would you propose?

; A person is a structure
;   (make-person symbol num symbol list-of-persons)

(define-struct person (name year eye-color kids))
Notice however, that we don't have a data definition for list-of-persons. What should we use?
A list-of-persons is either
  - empty, or
  - (cons person list-of-persons)
Notice that this data definition refers to the definition of person. If you were to draw the arrows, you'd see that these two data definitions are mutually dependent. In this case, the data definitions are called mutually recursive. What's an example of a family tree using this data definition?
(make-person 'Ian 1966 'blue empty)

(define ethan-info (make-person 'Ethan 2001 'blue empty))
(define elsa-info  (make-person 'Elsa  2000 'blue empty))

(define britt-info (make-person 'Britt 1969 'blue
                                (cons ethan-info (cons elsa-info empty)))

(make-person 'gordon 1938 'brown
             (cons (make-person 'Ian 1966 'blue empty)
                   (cons britt-info empty)))
What does a template look like over this pair of data definitions?
(define (p-func a-person)
  (person-name a-person) ...  
  (person-year a-person) ...  
  (person-eye-color a-person) ...  
  (lop-func (person-kids a-person)) ... )

(define (lop-func a-lop)
  (cond [(empty? a-lop) ...]
	[(cons? a-lop)
	 ... (p-func (first a-lop)) ... (lop-func (rest a-lop)) ... ]))
Notice that the template for a mutually recursive data definition contains one template for each constituent data definition. Furthermore, if there is a template for the datum returned by a selector, we add a call to the template for that datum. In this way, the calls in the template follow the arrows in the data definition, just as we've done for recursive functions. Write the program count-offspring, which consumes a person and returns a number. The returned number indicates how many people are in the family tree starting at person.
;; count-offspring : person -> num
;; compute how many people in family tree rooted at person
(define (count-offspring a-person)
  (+ 1 (count-offspring (person-kids a-person))))

;; count-peoples-offspring: list-of-persons -> num
;; compute how many people in family trees rooted at children
;;
(define (count-peoples-offspring a-lop)
  (cond [(empty? a-lop) 0]
	[(cons? a-lop)
	 (+ (count-offspring (first a-lop))
	    (count-peoples-offspring (rest a-lop)))]))
Write a program at-least-two-children which consumes a person and returns a list of names of all person with at least two children.
;; at-least-two-children : person -> list-of-symbol
;; returns a list of all people in tree with at least two children
(define (at-least-two-children a-person)
  (cond [(> (num-children (person-kids a-person)) 2)
	 (cons (person-name a-person) 
	       (children-with-two-children (person-kids a-person)))]
	[else (children-with-two-children (person-kids a-person))]))

;; children-with-two-children : list-of-children -> list-of-symbol
;; returns a list of all children with at least two children
(define (children-with-two-children a-lop)
  (cond [(empty? a-lop) empty]
	[(cons? a-lop)
	 (append (at-least-two-children (first a-lop))
		 (children-with-two-children (rest a-lop)))]))

;; num-children : list-of-children -> num
;; counts how many children are in the list
(define (num-children a-lop)
  (cond [(empty? a-lop) 0]
	[else (+ 1 (num-children (rest a-lop)))]))

ian -- can use visitors to illustrate mutual recursion (or, vice versa) -- for-cons and accept are mutually recursive.