Trees

Trees

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

georgetree.png

We defined a datatype for trees:

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

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

Let’s take a careful look at our function. This function is recursive–it’s calling itself on the “mother” and “father” subtrees. What happens when we call:

in-tree("Friedrich II", george-tree)

(See lecture capture for details)

More tree functions

What if we wanted to find the total number of people in the tree with a particular eye color?

fun count-eye-color(color :: String, tree :: AncTree) -> Number:
  cases (AncTree) tree:
    | unknown => 0
    | person(nm, ec, mo, fa) =>
      if ec == color:
        1 + count-eye-color(color, mo) + count-eye-color(color, fa)
      else:
        count-eye-color(color, mo) + count-eye-color(color, fa)
      end
  end
end

How about getting a list of all of the names in the tree?

fun list-names(tree :: AncTree) -> List<String>:
  cases (AncTree) tree:
    | unknown => empty
    | person(nm, ec, mo, fa) =>
      link(nm, L.append(list-names(mo), list-names(fa))
  end
end

In what order will this produce the names in the list? What if we wanted a different order?

Can we count the generations in the tree?

fun generations(tree :: AncTree) -> Number:
  cases (AncTree) tree:
    | unknown => 0
    | person(nm, ec, mo, fa) =>
      1 + num-max(generations(mo), generations(fa))
  end
end