;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname anc-tree) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) (require "student-extras.rkt") ; a video going over the first part of this material is at: ; http://www.youtube.com/watch?v=_Mrvxai7Bhg ; We want to represent anc-trees: ; ?? ?? ?? ?? ?? ?? ; \ / \ / \ / ; jackie.. ?? mona... abe... ; \ / \ / ; marge (b.1956, blue) homer (b.1955, brown) ; \ / ; bart (b.1979, brown eyes) ; datatype-defn: (define (unknown? v) ; interpretation: an unknown family-tree (empty) (and (symbol? v) (symbol=? 'unknown v))) (define (anc-tree? v) (or (unknown? v) (child? v))) (define-struct/c child ([name string?] [year integer?] ; birthyear [eye symbol?] [ma anc-tree?] [pa anc-tree?])) ; examples of data: (define dan (make-child "dan" 2000 'brown 'unknown 'unknown)) (make-child "chloe" 2023 'blue 'unknown dan) 'unknown ; template for *any* function handling an anc-tree (define/c (func-for-anc-tree a-tree) (-> anc-tree? ...?) (cond [(unknown? a-tree) ...] [(child? a-tree) (... (child-name a-tree) (child-year a-tree) (child-eye a-tree) (func-for-anc-tree (child-ma a-tree)) (func-for-anc-tree (child-pa a-tree)))])) ; TASK: ; Give a datatype definition, to process "ancestor trees" as above ; Give some examples of the data ; (again: "as data" meaning "actual code", or "actual values to pass to functions") ;;; To-model: ;;; a family tree, where a person's info is their name, birthyear, eye-color, and their parents' trees. ;;; Eventually, we have cases where somebody's parent is unknown. ;;; ; examples of anc-trees: 'unknown (make-child "Jackie" 1926 'brown 'unknown 'unknown) (make-child "Marge" 1956 'blue (make-child "Jackie" 1926 'brown 'unknown 'unknown) 'unknown) ; bind some anc-trees to variables, for easy test-cases later: ; (define jackie (make-child "Jackie" 1926 'brown 'unknown 'unknown)) (define marge (make-child "Marge" 1956 'blue jackie 'unknown)) (define abe (make-child "Abe" 1920 'blue 'unknown 'unknown)) (define mona (make-child "Mona" 1929 'brown 'unknown 'unknown)) (make-child "homer" 1955 'brown (make-child "Mona" 1929 'brown 'unknown 'unknown) (make-child "Abe" 1920 'blue 'unknown 'unknown)) (define homer (make-child "Homer" 1955 'brown mona abe)) (define bart (make-child "Bart" 1979 'brown marge homer)) ; To write: the function `size`: ; return the size of `tr` -- the number of `child` structs it contains. (check-expect (size 'unknown) 0) (check-expect (size (make-child "jackie" 1917 'brown 'unknown 'unknown)) 1) (check-expect (size (make-child "marge" 1956 'blue (make-child "jackie" 1917 'brown 'unknown 'unknown) 'unknown)) 2) (check-expect (size bart) 6) (define/c (size a-tree) (-> anc-tree? natural?) (cond [(unknown? a-tree) 0] [(child? a-tree) (+ (size (child-ma a-tree)) 1 (size (child-pa a-tree)))])) #;(define/contract (size tr) (-> anc-tree? natural?) ; Return the size of `tr` -- the number of `child` structs it contains. (cond [(unknown? tr) 'YOUR-CODE-HERE] [(child? tr) 'YOUR-CODE-HERE ])) (check-expect (size 'unknown) 0) (check-expect (size (make-child "Abe" 1920 'brown 'unknown 'unknown)) 1) (check-expect (size (make-child "Homer" 1955 'brown 'unknown (make-child "Abe" 1920 'brown 'unknown 'unknown))) 2) (check-expect (size 'unknown) 0) (check-expect (size jackie) 1) (check-expect (size marge) 2) (check-expect (size homer) 3) (check-expect (size bart) 6) ; Return a tree like `tr`, but with every name `name1` replace with `name2` (check-expect (change-name 'unknown "Joseph" "Joey") 'unknown) (check-expect (change-name jackie "Joseph" "Joey") jackie) (check-expect (change-name jackie "Jackie" "Jack") (make-child "Jack" 1926 'brown 'unknown 'unknown)) (check-expect (change-name (make-child "Joseph" 50 'blue (make-child "Amy" 50 'blue (make-child "Joseph" 50 'blue 'unknown 'unknown) (make-child "Joey" 50 'blue 'unknown 'unknown)) (make-child "Joseph" 50 'blue 'unknown 'unknown)) "Joseph" "Joey") (make-child "Joey" 50 'blue (make-child "Amy" 50 'blue (make-child "Joey" 50 'blue 'unknown 'unknown) (make-child "Joey" 50 'blue 'unknown 'unknown)) (make-child "Joey" 50 'blue 'unknown 'unknown))) (define/c (change-name a-tree before after) (-> anc-tree? string? string? anc-tree?) (cond [(unknown? a-tree) 'unknown] [(child? a-tree) (make-child (if (string=? before (child-name a-tree)) after (child-name a-tree)) (child-year a-tree) (child-eye a-tree) (change-name (child-ma a-tree) before after) (change-name (child-pa a-tree) before after))])) (define (all-names anc) #;(-> anc-tree? (listof string?)) ; Return a list of all the names in a tree. ; (cond [(unknown? anc) 'YOUR-CODE-HERE] [(child? anc) 'YOUR-CODE-HERE])) (check-expect (all-names 'unknown) '()) (check-expect (all-names jackie) (cons "Jackie" '()) ) (check-expect (sort (all-names marge) string<=?) (sort (cons "Jackie" (cons "Marge" '())) string<=?)) (check-expect (sort (all-names homer) string<=?) (sort (cons "Mona" (cons "Homer" (cons "Abe" '()))) string<=?)) (check-expect (sort (all-names bart) string<=?) (sort (cons "Jackie" (cons "Marge" (cons "Bart" (cons "Mona" (cons "Homer" (cons "Abe" '())))))) string<=?) ) ; LEFT AS PRACTICE: #| (define/contract (all-childs anc) (-> anc-tree? (listof child?)) ; Return a list of all the childs in a tree. ; ...) ;hint: `append` will append two lists. (check-expect (all-childs 'unknown) empty) (check-expect (all-childs abe) (list abe)) (check-expect (all-childs homer) (list mona homer abe)) (check-expect (sort (map child-name (all-childs bart) string<=?)) (sort (map child-name (list jackie marge bart mona homer abe)) string<=?)) ; Hmm, we should really sort these, as before; ; we can easily make `child<=?` which just compares to childs based on name. ; OR, we could just extract the names, and sort those. ; (This is imperfect, but reasonable as a hack: if the anc-tree had two different people ; with the same name, and the result instead had one of those in its answer twice, ; we wouldn't catch that error. We might even assuage this a bit, by having a test-case ; with an anc-tree `j` containing only two people j1 and j2, both named "Joe", and asserting that ; (or (equal? (all-childs j) (list j1 j2)) ; (equal? (all-childs j) (list j2 j1))) ; ...though at that point you might as well just have made a `child<=?` and done it properly. |# ; LEFT AS AN EXERCISE: #| (define (contains? anc target) (-> anc-tree? string? boolean?) ; Does the tree contain the given name, anywhere? ; ...) ;(check-expect (contains? ... "Bart") ...) (check-expect (contains? bart "Bart") true) (check-expect (contains? bart "Homer") true) (check-expect (contains? bart "Abe") true) (check-expect (contains? homer "Bart") false) (check-expect (contains? bart "Marge") true) (check-expect (contains? bart "Mona") true) (check-expect (contains? mona "Bart") false) |# ; challenge: write my-append: list, list -> list ; (Hint: follow the template for a list, ; but only take first/rest of arg1.) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; When traversing trees, many traditional (non-functional) books ; want to have a node-method do more than just call methods on its ; children; they want to reach down to its fields and do work using ; their info. The problem is, this requires ANOTHER check of whether ; the children are unknown-vs-Child -- the equivalent of a nested-cond. ; That doesn't smell right. ; ; It turns out, these problems can often be easily fixed by cleverly ; choosing a helper-function that takes in the node's info when recurring ; on its fields. ; Here are two examples: (define/contract (any-time-travelers? anc) (-> anc-tree? boolean?) ; (anybody born before their parents?)(*) ; (any-time-travelers-before-year? anc +inf.0)) (define/contract (any-time-travelers-before-year? anc last-year) (-> anc-tree? integer? boolean?) ; Are there either: somebody in the tree with a year before their parent's, ; OR the root's year is later than `last-year` ; (cond [(unknown? anc) false] [(child? anc) ; We can't compare our year with (child-year (child-ma anc)), ; because ma and pa might each be Unknown. ; rather than add two more sub-cases here, we'll pass our year ; to each of the sub-calls. (or (> (child-year anc) last-year) (any-time-travelers-before-year? (child-ma anc) (child-year anc)) (any-time-travelers-before-year? (child-pa anc) (child-year anc)))])) (check-expect (any-time-travelers? 'unknown) #false) (check-expect (any-time-travelers? abe) #false) (check-expect (any-time-travelers? (make-child "lexxor" 1801 "orange" (make-child "Scully" 1970 "brown" 'unknown 'unknown) 'unknown)) #true) ; TASK: write `oldest`, which returns the oldest child in the tree. ; [What is the most appropriate answer/action for 'unknown?] ; ; Hint: Think of how we wrote `max`, where we added a helper `max-of-or` ; which took in a list *and* a number, and returned the biggest number ; that was in-the-list-or-was-the-passed-in-number. (define/contract (entitle anc) (-> anc-tree? anc-tree?) ; Add "Mr." in front of each father's name, and "Ms." to each mother's, ; ... and "Baby" in front of the root's name. ; (entitle-help anc "Baby")) ; (Cf. walking an html tree and making some transform.) (define/contract (entitle-help anc title-for-root) (-> anc-tree? string? anc-tree?) ; Add `title-for-root` in front of the root's name, ; and "Mr. in front of each father's name, and "Ms." to each mother's. ; (cond [(unknown? anc) 'unknown] [(child? anc) ; We can't know if we are part of somebody else's 'ma' field ; or their 'pa' field. Rather than try to add back-pointers ; to the tree, the caller can pass one thing to its pa's ; and a different thing to its ma's: (make-child (string-append title-for-root " " (child-name anc)) (child-year anc) (child-eye anc) (entitle-help (child-ma anc) "Ms.") (entitle-help (child-pa anc) "Mr."))])) (check-expect (entitle 'unknown) 'unknown) (check-expect (entitle homer) (make-child "Baby Homer" 1955 'brown (make-child "Ms. Mona" 1929 'brown 'unknown 'unknown) (make-child "Mr. Abe" 1920 'blue 'unknown 'unknown))) ; challenge: write 'any-duplicates', which returns ; whether there are two childs with the same name, ; where one is an ancestor of another. ; (But a 'Kris' on the mother's side and a 'Kris' on the father's ; doesn't count.) #| /** How would we write this same data structure (and programs) in Java? * * Many standard texts use a single class Child; they represent * Unkowns with `null`, and they don't use the name `AncTree` because * they just thing "Child-or-null". * * This has several problems: * - you can't call methods on your empty-data. * - It's easy to be unclear about what parameters/fields/local-vars * are AncTrees and which are Childs -- because they're both just * being declared as type Child. (Using annotations `@nonnull` * and `@nullable` help ease this concern.) * - you have lots of checks for `null`, and code for empty-trees * and non-empty trees is all inside class Child * ... Moral: If you're checking * for null or using `instanceof`, you're not really being O.O. * * THE SOLUTION: "the composite pattern" is how to implement * union-types in O.O. We'll have `Unknown`, `Child`, and `AncTree` * all be actual Java classes (just like we wrote in racket-comments). * We'll have Unknown and Child each extend AncTree (to capture "is a"); * we'll make AncTree abstract (to capture "...is exactly one of these sub-types"). */ abstract class AncTree { } class Unknown extends AncTree { } class Child extends AncTree { String name; int year; // year-of-birth String eye; // eye-color, e.g. "brown" AncTree ma, pa; // mother, father /** standard constructor */ public Child( String _name, int _year, String _eye, AncTree _ma, AncTree _pa ) { this.name = _name; this.year = _year; this.eye = _eye; this.ma = _ma; this.pa = _pa; } public static void main( String[] args ) { AncTree abe = new Child( "Abe", 1920, "brown", new Unknown(), new Unknown() ); AncTree mona = new Child( "Mona", 1929, "blue", new Unknown(), new Unknown() ); AncTree homer = new Child( "Homer", 1951, "brown", mona, abe ); System.out.println( "Actual: " + abe.toString() ); System.out.println( "Expect: " + "new Child( \"Abe\", 1920, \"brown\", new Unknown(), new Unknown() )" ); System.out.println( "Actual: " + homer.toString() ); System.out.println( "Expect: " + "??" ); } /** @Override */ public String toString( /* Child this */ ) { return "new Child" + "( " + "\"" + this.name.toString() "\"" + ", " + this.year + ", " + "\"" + this.eye.toString() + "\"" + ", " + this.ma.toString() + ", " + this.pa.toString() + " )"; } /** @Override */ public int hashCode( /* Child this */ ) { int h = 0; h = h*SCRAMBLE + this.name.hashCode(); h = h*SCRAMBLE + this.year(); h = h*SCRAMBLE + this.eye.hashCode(); h = h*SCRAMBLE + this.ma.hashCode(); h = h*SCRAMBLE + this.pa.hashCode(); return h; } private static final int SCRAMBLE = 0b0001_1111; // == 31 decimal /* TODO: @Override `equals(Object)` */ } |# ; anctree->string: ;(check-expect (anctree->string abe) "[? Abe ?]") ;(check-expect (anctree->string abe) "new Child(Abe,") ;(check-expect (anctree->string abe) "new Child(Abe,") ; Mon: Define anc-tree. examples; template; size; all-names ; ; Wed: ; q: more examples of using map (w/ lambda) ; qz: what is the data-def'n of a FamTree ; self-quiz: toString ; start Java version of famTree ; ; Fri: ; Write: entitle (add a "Mr" or "Ms" to each name) ; ; Java: write `size` ; Note: the analog to `cond` is handled by the OO, which is good. ; If you're ever using 'instanceof' or using 'if ...== null', it's not really OO. ; Java: write `allNames` ; term: "composite design pattern" ; Java: write change-all-names ; ; Writing `equals` (in racket, then in Java) is interesting,