CS 312 Recitation 8
More Data Abstractions

An Abstraction for Rational Numbers

    signature FRACTION = sig
       (* A fraction is a rational number *)
	type fraction
	(* first argument is numerator, second is denominator *)
	val make : int -> int -> fraction
	val numerator : fraction -> int
	val denominator : fraction -> int
	val toString : fraction -> string
	val toReal : fraction -> real
	val add : fraction -> fraction -> fraction
	val mul : fraction -> fraction -> fraction
    end

(* Here's one implementation of fractions -- what can go wrong here? *)
structure Fraction1 :> FRACTION =
    struct
	type fraction = { num:int, denom:int }

	fun make (n:int) (d:int) = {num=n, denom=d}

	fun numerator(x:fraction):int = #num x

	fun denominator(x:fraction):int = #denom x

	fun toString(x:fraction):string =
	    (Int.toString (numerator x)) ^ "/" ^
	    (Int.toString (denominator x))

	fun toReal(x:fraction):real =
	    (Real.fromInt (numerator x)) / (Real.fromInt (denominator x))

	fun mul (x:fraction) (y:fraction) : fraction =
	    make ((numerator x)*(numerator y))
	         ((denominator x)*(denominator y))

	fun add (x:fraction) (y:fraction) : fraction =
	    make ((numerator x)*(denominator y) +
		  (numerator y)*(denominator x))
		 ((denominator x)*(denominator y))
    end

A Better Abstraction for Rational Numbers

(* First, we could give 0 as the denominator -- this is a bad fraction.
 * Second, we're not reducing to smallest form.  So we could overflow
 * faster than we need to.
 * Third, we're not consistent with the signs of the numbers.  Try
 * make ~1 ~1.
 *
 * We need to pick some representation invariant
 * that describes how we're going to represent legal fractions.
 * Here is one choice that tries to fix the bugs above.
 *)
structure Fraction2 :> FRACTION =
    struct
	(* Rep invariant:
	 *  (1) denom is always positive
	 *  (2) always in most reduced form *)
	type fraction = { num:int, denom:int }

	(* Algorithm due to Euclid:  for positive numbers x and y,
	 * find the greatest-common-divisor.*)
	fun gcd (x:int) (y:int) : int =
	    if (x = y) then x
	    else if (x < y) then gcd x (y - x)
		 else gcd (x - y) y

	exception BadDenominator

	fun make (n:int) (d:int) : fraction =
	    if (d = 0) then raise BadDenominator
	    else let val g = gcd (abs n) (abs d)
		     val n2 = n div g
		     val d2 = d div g
		 in
		     if (d2 < 0) then {num = ~n2, denom = ~d2}
		     else {num = n2, denom = d2}
		 end

	fun numerator(x:fraction):int = #num x

	fun denominator(x:fraction):int = #denom x

	fun toString(x:fraction):string =
	    (Int.toString (numerator x)) ^ "/" ^
	    (Int.toString (denominator x))

	fun toReal(x:fraction):real =
	    (Real.fromInt (numerator x)) / (Real.fromInt (denominator x))

	(* notice that we didn't have to re-code mul or add --
	 * they automatically get reduced because we called
	 * make instead of building the data structure directly.
	 *)
	fun mul (x:fraction) (y:fraction) : fraction =
	    make ((numerator x)*(numerator y))
	         ((denominator x)*(denominator y))

	fun add (x:fraction) (y:fraction) : fraction =
	    make ((numerator x)*(denominator y) +
		  (numerator y)*(denominator x))
		 ((denominator x)*(denominator y))
    end
 

A Dictionary Abstract Data Type

(* Here's a signature for dictionaries.  A dictionary is a container
 * that holds keys and associated objects.  These dictionaries are
 * polymorphic (i.e., we can use them for any type.)  Here we've
 * defined keys to be strings.
 *)
signature DICTIONARY =
    sig
	type key = string
	type 'a dict

	(* make an empty dictionary carrying 'a values *)
	val make : unit -> 'a dict

	(* insert a key and value into the dictionary *)
	val insert : 'a dict -> key -> 'a -> 'a dict
	(* lookup a key in the dictionary -- raise NotFound if the
	 * key is not present.
	 *)
	val lookup : 'a dict -> key -> 'a
	exception NotFound

    end

(* One implementation:  an association list [(key1,x1),...,(keyn,xn)] *)
structure AssocList :> DICTIONARY =
    struct
	type key = string
	type 'a dict = (key * 'a) list

	fun make():'a dict = []

	fun insert (d:'a dict) (k:key) (x:'a) : 'a dict = (k,x)::d

	exception NotFound

	fun lookup (d:'a dict) (k:key) : 'a =
	    case d of
		[] => raise NotFound
	      | ((k',x)::rest) =>
		    if (k = k') then x
		    else lookup rest k
    end

(* This implementation seems a little better for looking up values *)
structure SortedAssocList :> DICTIONARY =
    struct
	type key = string
	(* rep invariant: the list is sorted by key and
        * each key occurs only once in the list. *)
	type 'a dict = (key * 'a) list

	fun make():'a dict = []

	fun insert (d:'a dict) (k:key) (x:'a) : 'a dict =
	    case d of
		[] => (k,x)::nil
	      | (k',x')::rest =>
		    (case String.compare(k,k') of
			 GREATER => (k',x')::(insert rest k x)
		       | EQUAL => (k,x)::rest
		       | LESS => (k,x)::(k',x')::rest)

	exception NotFound

	fun lookup (d:'a dict) (k:key) : 'a =
	    case d of
		[] => raise NotFound
	      | ((k',x)::rest) =>
		    (case String.compare(k,k') of
			 EQUAL => x
		       | LESS => raise NotFound
		       | GREATER => lookup rest k)
    end

(* This one uses a binary tree to keep the data -- the hope is
 * that inserts or lookups will be proportional to log(n) where
 * n is the number of items in the tree.
 *)
structure AssocTree :> DICTIONARY =
    struct
	type key = string
	(* Invariant: for Nodes, data to the left have keys that
        * are LESS than the datum and the keys of
	 * the data to the right. *)
	datatype 'a dict = Empty | Node of {key: key,datum: 'a,
					    left: 'a dict,right: 'a dict}
	fun make():'a dict = Empty

	fun insert (d:'a dict) (k:key) (x:'a) : 'a dict =
	    case d of
		Empty => Node{key=k, datum=x, left=Empty, right=Empty}
	      | Node {key=k', datum=x', left=l, right=r} =>
		    (case String.compare(k,k') of
			 EQUAL =>
			     Node{key=k, datum=x, left=l, right=r}
		       | LESS =>
			     Node{key=k',datum=x',left=insert l k x,
				  right=r}
		       | GREATER =>
			     Node{key=k',datum=x',left=l,
				  right=insert r k x})

	exception NotFound

	fun lookup (d:'a dict) (k:key) : 'a =
	    case d of
		Empty => raise NotFound
	      | Node{key=k',datum=x, left=l, right=r} =>
		    (case String.compare(k,k') of
			 EQUAL => x
		       | LESS => lookup l k
		       | GREATER => lookup r k)
    end