Family trees

(perhaps better called "ancestor trees").
Draw some examples:
?     ?     ?   ?  ?      ?
 \   /       \ /    \    /
'jackie      'mona  'abe
 (1926)  ?   (1929) (1920)
    \   /       \   /
   'marge      'homer
    (1956)      (1955)
        \      /
         'bart
         (1979)
(Note: We don't know the name of Grampa Bouvier, marge's father, but he died in a roller-coaster accident.) (Birthdates fabricated, but names courtesy of springfield nuclear power plant's web site.) Miscellaneous vocabulary word: While Abe was an enlisted man in WWII, his squad, the flying hellfish, found a a cache of stolen paintings, and they formed a tontine ("an investment plan in which participants buy shares in a common fund and recevie an annuity, with the entire fund going to the final survivor or to those who survive after a specified time. [Fr., after Lorenzo Tonti (1635-90?), Naples-born French banker]." Source: American Heritage College Dictionary)
Some simpler family trees:
?      ?
 \    /
 'jackie
  (1926)
and
?     ?
 \   /
'jackie   
 (1926)  ?
    \   /
   'marge
   (1958)
What might be considered the simplest possible family tree?

Definition: a FamTree is

Note: Be sure to differentiate the two different concepts: "child", which is somebody whose name we know, vs "FamTree", which might be a child or 'unknown.

to do:: write the define-struct for child.

Examples:


Looking for a parallel to lists:
purpose list functions tree functions (provided to you)
pre-existing values empty 'unknown
build one out of smaller parts cons: any, list --> list make-child: symbol, FamTree, FamTree, number --> child
selectors
first : cons --> any
rest : cons --> list
child-ma: child --> FamTree
child-pa : child --> FamTree
child-name : child --> symbol
child-birthyear : child --> number
predicates
list? : any --> boolean
empty? : any --> boolean
cons? : any --> boolean
FamTree?(*) : any --> boolean
unknown?(*) : any --> boolean
child? : any --> boolean
(*) The indicated functions aren't made for us via the define-struct; we can write them ourselves easily enough, though.

FamTree template

Okay, now that we have examples of the data, we're going to write a function that takes in a FamTree, and returns [mumble-cough-mumble]. Go!
;; famTree-func: FamTree --> ???
;;
(define (famTree-func a-ft)




                                                          )

Functions dealing with FamTrees

Write the following; don't peek at solutions until you have answered the suggested qeustions to yourself!
  1. ;; (size ft) : FamTree --> number
    ;; Return how many (non-unknown) children ft contains.
    ;;
    (define (size a-ft)
       (cond [(unknown? a-ft)   ...]
             [(child?   a-ft)   ..(child-name a-ft)..(child-birthyear a-ft)..
                                ..(size (child-ma a-ft))..(size (child-pa a-ft))..]))
    
    
    
    
    (= (size jackie-tree ..)
    (= (size bart-tree)  ..)
    ; Other test cases?  One important test is missing!
    
    
    
    
    What type of thing is (child-ma a-ft) (list, number, FamTree, symbol, ...?)
    Yep -- it's a FamTree. And what can we do with FamTrees? Yep -- call size on them, as indicated
    Now: What type of thing is (size (child-ma a-ft)) (list, number, FamTree, symbol, ...?)
    Use the pieces indicated (size of mother, father and the name) to determine the size of the whole thing. (Hint: the name doesn't matter, for this particular problem.)
    (How would this code change, if we wanted the size including 'unknowns?)
  2. ;; (related-to-abe? ft): FamTree --> boolean
    ;; Is 'abe the name of anybody in the FamTree "ft"?
    ;;   (Perhaps "descendent-of-abe?" would be a better name.)
    ;;
    (define (related-to-abe? a-ft)
       (cond [(unknown? a-ft)   ...]
             [(child?   a-ft)   ..(child-name a-ft)..(child-birthyear a-ft)..
                                ..(related-to-abe? (child-ma a-ft))..
                                ..(related-to-abe? (child-pa a-ft))..]))
    
    
    (boolean= (related-to-abe? jackie-tree)  ...)
    (boolean= (related-to-abe? bart-tree)    ...)
    
    
    Ask yourself the same questions as before, when filling in the parts.
    (solution)
  3. How about a program to count the female ancestors in a tree? (Another miscellaneous vocabulary word: distaff refers to the female side of a family. (The etymology reflects medieval roles: the distaff originally meant the staff used to hold the wool or flax, in spinning. (dictionary))
    ;; cfa-version1 : FamTree --> num
    ;; Count how many female ancestors are in the tree a-ftn.
    ;;
    (define (cfa-version1 a-ftn)
      (cond [(unknown? a-ftn) 0]
            [else (cond [(empty? (child-mother a-ftn))
                         (cfa (child-father a-ftn))]
                        [else (+ 1
                                 (cfa (child-mother a-ftn))
    		  	     (cfa (child-father a-ftn)))])]))
    
    Anything wrong with this program? Yes. It violates our rule of opening only one data definition per program. This one looks at the cases in the mother sub-tree. Therefore, we should use a helper function, as follows.
    ;; cfa-version2 : FamTree --> num
    ;; Count how many female ancestors are in a-ftn.
    ;;
    (define (cfa-version2 a-ftn)
      (cond [(unknown? a-ftn) 0]
    	[else (+ (count-mother (child-mother a-ftn))
    		 (cfa (child-mother a-ftn))
    		 (cfa (child-father a-ftn)))]))
    
    ;; count-mother : FamTree --> num
    ;; Determines how many ancestors to add based on the root of a-ftn
    ;; -- either 0 or 1.
    ;;
    (define (count-mother a-ftn)
      (cond [(unknown? a-ftn) 0]
    	[else             1]))
    
    What if we wanted to count only female ancestors born after 1950? What in the above program changes? Only the helper function, which would change as follows:
    ;; count-if-young-enough: child -> num
    ;; Returns 1 if node has blue eyes, else 0.
    ;;
    (define (count-if-young-enough a-child)
      (cond [(< 1950 (child-birth-year a-child))   1]
            [else                                  0]))
    
    ;; count-mother : FamTree -> num
    ;; Determines how many female ancestors to add based on a-ftn.
    ;;
    (define (count-mother a-ftn)
      (cond [(empty? a-ftn) 0]
    	[else (count-if-young-enough a-ftn)]))
    
    Note that you're welcome to coalesce these two functions. If you want, you can even collapse the nested conds. But be careful -- if you start collapsing nested conds, it opens the door for a mistake (you collapse them incorrectly), and it becomes less clear to the reader that you are doing your case analysis correctly.

    Finally, here is a version which doesn't require looking inside the mother's FamTree to see if it's unknown. Instead, it uses an extra argument.

    ;; cfa-version3 : FamTree --> num
    ;; Count how many female ancestors are in a-ftn.
    ;;
    ;; How it works [this is NOT part of the purpose]:
    ;; just call help-cfa, and indicate that the root of the tree
    ;; is not to be considered a female ancester.
    ;; (If you have extra knowledge that the person at the root
    ;; of a-ft is female, and you want to include her in the count,
    ;; use 1 instead of 0.)
    ;;
    (define (cfa-version3 a-ft)
      (help-cfa a-ft 0))
    
    ;; help-cfa: FamTree, num --> num
    ;; Return how many female ancestors are in a tree,
    ;; where the root itself is either considered 0 or 1
    ;; female ancestors; this 0 or 1 is provided
    ;; as the argument "mothers-at-root".
    ;;
    ;; How it works [this paragraph is NOT part of the purpose]:
    ;; This extra info is provided by the person who called us -- since they
    ;; might know whether our root is somebody else's mother.)
    ;; When we make a recursive call, we know whether each subtree
    ;; has our mother at the root, or our father, and we can
    ;; call with mothers-at-root set appropriately.
    ;; 
    (define (help-cfa a-ft mothers-at-root)
      (cond [(unknown? a-ft) 0]
            [(child? a-ft) (+ mothers-at-root
                              (help-cfa (child-pa a-ft) 0)
                              (help-cfa (child-ma a-ft) 1))]))
    
    This use of an extra argument is actually a limited case of what we call an "accumulator"; we'll talk about the technique of using accumulators later, in lecture 15.

    Note that it's fine to include comments how a function works. However, always distinguish "what a function computes" from "how it computes it". The first matters to others, the second doesn't.

    Finally, a note on programming style: count-female-ancestors is a bit tricky to get right, the way we need to figure out whether we're approaching a sub-tree from the mother's side, or the father's side. Why is this? Well, sometimes problems inherently require some roundabout work. In this case, though, trying to count female ancestors would have been trivial, if we'd also known the gender of each child. There are two contradictory factors to be balanced:

    There is a compromise between these two positions, which might actually be reasonable in this case: Include the extra information (gender), but then write a program that does a sanity-check on the trees that are created -- namely, it makes sure that the data doesn't seem to be inconsistent. (For instance, we could replace calls to make-child with our own function make-child-safely, which makes sure ma is female and pa is male, and if so it just goes and calls the underlying built-in make-child.)

    There are many solutions, some more obvious than others, some cleverer than others, some easier to understand than others, and some better than others. (Keep in mind that clever does not mean easier to understand!) But the most important step, is getting a data defintion which models the problem you want to solve!

    [Lecture ended here. --ian, 01.fall]
  4. ;; 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.)
    ;;
    
    
    
    
    
    
    
    
    
    
  5. Optional examples: Topiaries

    Here are some further exercises, if you're bored: