Rice University

COMP 210: Principles of Computing and Programming

Lecture #14       Fall 2003

Review

Template for a structure that contains another structure does not violate the contained structure's encapsulation (for example):

(define-struct StructA (anInt aSym aStructB))
#|
Template:

(define (f-StructA aStructA)
    (...(StructA-anInt aStructA)...(StructA-aSym aStructA)
     ...(f-StructB (StructA-aStructB aStructA))...)) 

|#

The template for a structure that contains multiple structures follows directly from above:

(define-struct StructA (anInt aSym aStructB aStructC))
#|
Template:

(define (f-StructA aStructA)
    (...(StructA-anInt aStructA)...(StructA-aSym aStructA)
     ...(f-StructB (StructA-aStructB aStructA))
     ...(f-StructC (StructA-aStructC aStructA))....) 

|#

The template for an abstract structure that contains the cond statement to separate the different concrete cases and does not violate the encapsulation of the concrete sub-types:

;; A abstract struct StructA is either
;; -- a StructX
;; -- a StructY
;; -- a StructZ

#|
Template:

(define (f-StructA aStructA)
    (cond
        [(StructX? aStructA) (... (f-StructX aStructA)...)]
        [(StructY? aStructA) (... (f-StructY aStructA)...)]
        [(StructZ? aStructA) (... (f-StructZ aStructA)...)]))

|#

Note: The HTDP book includes the selectors for the concrete functions in the above template and functions on any internal structures, which is fine for simple or often used structure systems. The above template is more stringently enforces encapsulations and fits better with the "Process Flow Analysis" we will do shortly in the course. The standard template for a list, which is an abstract structure, does follow the template for an abstract structure as per HTDP, but does not follow the above template exactly.

Note that accessor (selector) functions on a given structure are functions on that structure.


See the Exam 1 solution for more examples.

 

Family Trees

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 (label))

(define UNKNOWN (make-Unknown '?))    

;; a Child is a structure 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-label 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- 
                                          (make-Child 'Bubba 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)) 
                          (count (child-pa ft)))]))

"count tests:"
(= 0 (count UNKNOWN))
(= 7 (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 " |_ ")
(define newline-string (make-string 1 #\newline)) ;; a string with just a linefeed

;; FTtoString: FamTree --> string
;; Returns a string representation of a family tree 
(define (FTtoString ft)
    (cond
        [(Unknown? ft) (string-append newline-string 
                                     (symbol->string (Unknown-label 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-label 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.

You must be in Advanced Student language level to use display.

Example:

(display (FTtoString Bart))

(display (FTtoString UNKNOWN))

The code for today can be downloaded here.

 

 

 

©2003 Stephen Wong