Suppose one wanted to model a genealogical family tree with parents, children, uncles, aunts, grandparents, etc.
(Genealogical information courtesy of the Springfield Nuclear Power Plant's web site.)
???? - ???? ???? - ???? ???? - ???? / \ \ / Gladys Jackie - ???? Mona - Abe / | \ / \ Patty Selma Marge - Homer Herbert / | \ Bart Lisa Maggie
As anyone who has ever attempted to fill in a chart of all one's known relatives will attest, this can quickly get very complicated.
Every known person has a mother and a father.
That is, every known person is a child.
What are some attributes (fields) of a child?
But a what is a mother and a father?
They're both children!
I smell a recursive definition coming up....except that it is doubly so, since a child structure would contain two recursive elements.
What's missing?
The base case. Where is it?
The family tree terminates when an unknown relative is encountered. Unknown people have no known parents since we don't know who they are in the first place.
A family tree starting at Bart, thus looks like this:
? ? ? ? ? ? \ / \ / \ / 'Jackie 'Mona 'Abe (1926) ? (1929) (1920) \ / \ / 'Marge 'Homer (1956) (1955) \ / 'Bart (1979)
Why does it not matter that we seem to have lost some data w.r.t. the original diagram above?
Our data definition for a family tree is thus:
;; A FamTree is ;; - Unknown ;; - (make-child sym num FamTree FamTree) ;; Unknown is an empty FamTree (define-struct Unknown (name)) (define UNKNOWN (make-Unknown '?)) ;; a Child is a (display where ;; name is the name of a person ;; year is their birthyear ;; ma is a FamTree representing the person's mother ;; pa is a FamTree representing the person's father ;; (make-Child sym num FamTree FamTree) (define-struct Child (name year ma pa))
The template for a family tree is thus
(define (f-FamTree ft...) (cond [(Unknown? ft) (...(Unknown-name ft)...)] [(child? ft) ((Child-name ft)...(Child-year ft)...(f-FamTree (Child-ma ft)...)...(f-FamTree (Child-pa ft)...))]))
It's really no different than any other recursive data structure we've seen.
The UML diagram for the system looks familiar enough.
Let's instantiate Bart:
(define Bart (make-Child 'Bart 1979 (make-Child 'Marge 1956 (make-Child 'Jackie 1926 UNKNOWN (make-Child 'Bubba 1908 UNKNOWN UNKNOWN)) UNKNOWN ) (make-Child 'Homer 1955 (make-Child 'Mona 1929 UNKNOWN UNKNOWN) (make-Child 'Abe 1920 UNKNOWN (make-Child 'Bubba 1920 UNKNOWN UNKNOWN)))))
A printout of Bart shows us this:
(shared ((-3- (make-Unknown '?))) (make-Child 'Bart 1979 (make-Child 'Marge 1956 (make-Child 'Jackie 1926 -3- -3-) -3-) (make-Child 'Homer 1955 (make-Child 'Mona 1929 -3- -3-) (make-Child 'Abe 1920 -3- -3-))))
The (shared ((-3- (make-Unknown '?))) ....) is how Scheme tells us that there is a single unknown structure that is appearing multiple times in the larger structure. There is a switch in under the Languages/Choose Language.../Show Details options where you can suppress the display of the shared function.
Let's write a function to count all the known people in the FamTree
;;count: FamTree --> num ;; counts all the known people in a family tree. (define (count ft) (cond [(Unknown? ft) 0] [(Child? ft) (+ 1 (count (Child-ma ft)) (Child (child-pa ft)))])) "count tests:" (count UNKNOWN) (count Bart)
Here's one of the best accumulator style algorithms I've ever seen (thanks to Dr. Nguyen):
;; prefix values used by FTtoString (define ma-prefix " | ") (define pa-prefix " ") (define dat-prefix " |_ ") ;; FTtoString: FamTree --> string ;; Returns a string representation of a family tree (define (FTtoString ft) (cond [(Unknown? ft) (string-append newline-string (symbol->string (Unknown-name ft)) newline-string)] [(Child? ft) (string-append newline-string (symbol->string (Child-name ft)) " " (number->string (Child-year ft)) newline-string (FTtoString_help (Child-ma ft) ma-prefix) newline-string (FTtoString_help (Child-pa ft) pa-prefix) newline-string)])) ;;FTtoString: FamTree, string --> string ;;returns a string representation of a family tree ;; where the prefix string is pre-pended onto every parent's row (define (FTtoString_help ft prefix) (cond [(Unknown? ft) (string-append dat-prefix (symbol->string (Unknown-name ft)))] [(Child? ft) (string-append dat-prefix (symbol->string (Child-name ft)) " " (number->string (Child-year ft)) newline-string prefix (FTtoString_help (Child-ma ft) (string-append prefix ma-prefix)) newline-string prefix (FTtoString_help (Child-pa ft) (string-append prefix pa-prefix)))]))
:Note: Strings containing multiple lines (i.e. have imbedded newline-strings) will not display correctly in ver. 202 of DrScheme. You need to used (display [string] ) to get them to show correctly on the screen. Display will cause the string to be displayed inside a bounded text box.
Example:
(display (FTtoString Bart))
(display (FTtoString UNKNOWN))
The code for today can be downloaded here. New and improved!! Now works with ver. 202!
©2002 Stephen Wong