Trees

Last time, we closed by talking about storing tree data. Our example was the family tree of George III of the United Kingdom:

georgetree.png

We talked about using a table:

name eye-color mother father
“George” “green” “Auguste” “Frederick”
“Auguste” “green” “Magdalena” “Friedrich II”
“Frederick” “brown” “” “”

Using this table is going to be quite inconvenient, though. How would we look up a person’s grandparents? We’d have to do a filter-with to find their parents, then another filter-with to find parents of those parents.

How about a datatype?

data AncTree:
  | person(name :: String, eye-color :: String, mother :: ..., father :: ...)
end

What should we put as the type of the mother and father fields? Does String work?

If we use String, we’re basically left off where we were before–we can have a List<AncTree> that we can use like our table. What if we did this?

data AncTree:
  | person(name :: String, eye-color :: String, mother :: AncTree, father :: AncTree)
end

The parents of an AncTree instance are now themselves AncTree.

Let’s write George’s family tree:

george-tree = person("George III", "green",
  person("Auguste", "green",
    person("Magdalena", "blue",
      person("Sophia", "green", ...

What should we put for Sophia’s parents? Does "" work?

We’re going to need to define a new constructor:

data AncTree:
  | person(name :: String, eye-color :: String, mother :: AncTree, father :: AncTree)
  | unknown
end

So now we can say

george-tree = person("George III", "green",
  person("Auguste", "green",
    person("Magdalena", "blue",
      person("Sophia", "green", unknown, unknown),
      unknown),
    person("Friedrich II", "brown", unknown, unknown)),
  person("Frederick", "green", unknown, unknown))

Now that we’ve defined this data type, how can we use it? Let’s write a function to find a specific name in the tree:

fun in-tree(name :: String, tree :: AncTree) -> Boolean:
  ...
where:
  in-tree("George III", george-tree) is true
  in-tree("Sophia", george-tree) is true
  in-tree("Doug", george-tree) is false
end

How are we going to complete this function? We’re dealing with a datatype, so we know we’re going to need cases:

fun in-tree(name :: String, tree :: AncTree) -> Boolean:
  cases (AncTree) tree:
    | unknown => ...
    | person(nm, ec, mo, fa) => ...
  end
where:
  in-tree("George III", george-tree) is true
  in-tree("Sophia", george-tree) is true
  in-tree("Doug", george-tree) is false
end

What should we put in each case?

fun in-tree(name :: String, tree :: AncTree) -> Boolean:
  cases (AncTree) tree:
    | unknown => false
    | person(nm, ec, mo, fa) =>
      (nm == name) or in-tree(name, mo) or in-tree(name, fa)
  end
where:
  in-tree("George III", george-tree) is true
  in-tree("Sophia", george-tree) is true
  in-tree("Doug", george-tree) is false
end

This suggests a new template for ancestor-tree processing functions (like our template for list processing functions, but for trees):

  fun fun-name(..., tree :: AncTree) -> ...:
    cases (AncTree) tree:
      | unknown => ...
      | person(nm, ec, mo, fa) =>
        ... fun-name(..., mo) ... fun-name(..., fa)
    end
end