Due on Monday, Jan. 29, 9:30AM
Numbered questions are to be turned in by the date and time specified
to the TA in charge of homework grading coordination (TBA). Questions
marked with
's are for personal
consideration or laboratory discussion.
General trees as a recursive polymorphic datatype. General trees arise when we wish to consider the situation in which a node can have more, or less, than exactly two subtrees (or ``children'' according a widely used terminology inspired by family trees), as was the case with binary trees. In our representation, we group the children of a node into a list. We will not need to consider leaves separately from nodes: the list of children of a node may simply be empty. In addition, we make no commitment about the type of the content of each node, using a type variable, and hence defining a type polymorphic trees. The declaration is compact and elegant:
datatype 'a tree = Node of 'a * ('a tree) list
The constructor Node together with the constructors for lists allow us to easily define values of this type, which are general trees:
val atree = Node(3,[Node(2,[]),
Node(4,[Node(8,[]),
Node(11,[])]),
Node(5,[])])
val atree = Node (3,[Node (#,#),Node (#,#), Node (#,#)]) : int tree
The purpose of this exercise is to write traversal functions, that is, functions of type 'a tree -> 'a list that produce a list of the contents of tree nodes in a specific order.
In depth-first traversal, a node n and all its descendants are visited before visiting the nodes that follow n in the list of siblings to which n belongs. In breadth-first traversal, the nodes of an entire sibling list are visited before any descendants of these nodes. Note that these definitions identify uniquely the list produced by a breadth-first traversal, while for depth-first traversal we need to further specify whether the content of a node is listed before or after that of its descendants.
Turn in: The three traversal functions. The result of running them on a general tree with about 20 nodes.
(For additional fun.) In a general tree, a level is
defined as the set of all nodes that have the same distance to the
root. The breadth of the tree is defined as the number of nodes
in its largest level. Write a function that calculates the breadth of
a general tree.
Structures and signatures. You must be careful to load signatures and structures in a order that ensures each name is mentioned only after it is declared. A common problem is introducing a structure before its signature. Another is mentioning a structure that has not been loaded into the current environment.
signature PRODUCTCOMPLEX =
sig
val product : CartComplex.complex list -> CartComplex.complex
end
where product is a function that takes a list of complex numbers
and computes their product. For instance:
ProductComplex.product [CartComplex.descartes (1.0, 2.0),
CartComplex.descartes (3.0, ~1.0)]
returns the product of 1.0+2.0i and 3.0-1.0i. You do this by
filling in the code in an expression like the following one:
structure ProductComplex : PRODUCTCOMPLEX =
struct
fun product ...
end
To compile your module, you must have both COMPLEX and
CartComplex in your environment. Create a file that has an
example in it and write a script to load the files holding the
declarations and example to produce a transcript of your function in
use.
Turn in: your structure, your script, and an example of the use of your interface function on a list with at least three elements.
This process may seem somewhat tedious for the example
above, but when your program is large it may be the only practical way
to proceed.
Exploiting linear orders.
A linear order
is
a binary relation that satisfies four properties. First, it is
reflexive:
x
x.
Second, it is anti-symmetric:
x
y and
y
x implies
x=y.
Third, it is transitive:
x
y and
y
z implies
x
z.
Fourth, it is linear:
either x
y
or y
x.
In SML, it is natural to represent a linear ordering on elements of a
type t as a function of type t * t -> bool. Linear
orderings are often useful in optimizing searches.
What are some examples of linear orders? What are some
examples of function having type t * t -> bool that are not
linear orders?
We also need to talk about equality. It is not always possible to tell if two elements of a type are equal. For instance, this is `counter-recommended' for real values (because of possible rounding error) and impossible for function spaces.
Why is it impossible to tell whether two functions of type
int -> int are equal even though it is possible to tell if two
elements of type int are equal? What do we mean by ``tell'' in
the previous two sentences?
Is it possible to give a linear ordering for the functions
of type int -> int? Why is it dangerous to use vague
formulations like ``give'' in the previous sentence and ``tell'' in
the previous paragraph when we try to express precise facts?
SML has a distinguished notation for type variables ranging over types on which equality is defined. These are marked with two quote symbols, ''a, rather than the more general case, 'a, that uses only one.
signature GROUPBY =
sig
val eq : 'a list * ('a -> ''b) -> (''b * 'a list) list
val comp : a' list * ('a -> 'b) * ('b * 'b -> bool) -> ('b * 'a list) list
end
by filling in the necessary code in the following structure declaration:
structure GroupBy : GROUPBY =
struct
fun eq ...
fun com ...
end
The semantics of the two interface operations is described as follows.
Applying the function eq to a pair (l, f ) returns a
list of pairs (x, f -1
(x)) such that x
is an element of type ''b
and f -1(x) is a
non-empty list of all of the elements from
l that f maps to x. For example,
GroupBy.eq ([1,4,6,7,9,45,32], even)gives
[(true, [4,6,32]), (false, [1,7,9,45])]and
GroupBy.eq (["one", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten", "eleven"],
size)
gives
[(6, ["eleven"]), (3, ["one", "two", "six", "ten"]), (4, ["four", "five", "nine"]), (5, ["three", "seven", "eight"])]The function comp is essentially the same, but it is given a third parameter which is assumed to be a linear ordering on the type 'b. You must find a way to use this additional parameter in order to achieve a more efficient implementation than eq.
Turn in: Your structure and examples illustrating the use of the eq and comp functions in its interface.
What might happen if the the third parameter of GroupBy.comp
failed to be a linear order?