15-212-ML : Homework Assignment 4

Due Wed Oct 21, 12 noon (electronically).

Maximum Points: 100 (+20 extra credit)


In this assignment you will be constructing an implementation of the Boggle® word game. This will test, one more time, your understanding of data structures and algorithms and how to cast them into ML code. But it will also test your understanding of the module system, including functors, signature ascription, and where type instantiation. You must adhere to the following principles:

Problem 1: Tries (30 points)

A trie is an efficient data structure for indexing data based on lists of ordered keys. A particularly useful instance of this idea considers a string as a list of characters.

Entries are found in a trie by starting at the node and following the appropriate branches until a labeled node is found. As an example, consider a trie indexed by lists of characters, where the data entry stored at a labeled node is the string representation of the word. For example, the string "badge" is found in the following tree by taking the path #"b", #"a", #"d", #"g", #"e".

In order to make searching tries more efficient the branches emanating from any node are sorted by their label.

Tries can implement all the operations of dictionaries (as considered in the previous assignment). For their efficient use, however, an implementation of tries should export at least one other operation, namely to extract the subtrie rooted at the node addressed by a key. We call this operation sub.

The construction of the signature of tries expresses concisely that every trie can be considered as a dictionary whose keys are lists. The signatures can also be found in the file trie.sml.

signature DICT =

  type key				(* parameter *)
  type 'a entry = key * 'a

  type 'a dict				(* abstract type *)

  val empty : 'a dict
  val insert : 'a dict -> 'a entry -> 'a dict
  val lookup : 'a dict -> key -> 'a option

  val foldl : ('a entry * 'b -> 'b) -> 'b -> 'a dict -> 'b
  val app : ('a entry -> unit) -> 'a dict -> unit

end;  (* signature DICT *)

signature TRIE =

  type key1				(* parameter *)
  include DICT where type key = key1 list

  val sub : 'a dict -> key -> 'a dict option

end;  (* signature TRIE *)
Implement a functor Trie which takes a structure of signature ORDER (see file trie.sml) and returns a structure implementing the signature TRIE satisfying the following specification.
type key1
is the ordered type which is the parameter to the structure.
type 'a dict
is the abstract type of tries with entries of type 'a.
empty, insert, and lookup
are as for dictionaries.
foldl f init trie == f (en, f(..., f(e1, init)))
where e1,...,en is an enumeration of the entries in trie in alphabetical order. This is a generalization of the function List.foldl from the standard library.
app f trie => ()
applies f to every entry in trie in alphabetical order. (Hint: use foldl to program app.)
sub trie key => SOME(subtrie) or NONE
where subtrie is the subtrie located at the address described by key in trie. It should be NONE if there is no such subtrie.
For example, if the trie depicted above is t, then sub t [#"i", #"c"] should return SOME of the following trie, while sub t [#"i", #"t"] should return NONE.

Problem 2: Random Number Generation (5 points)

Using the random number generator described on page 108 in Paulson implement a structure named Random containing a random number generator that conforms to the following signature:

signature RANDOM =
  type gen
  val init : int -> gen
  val randomNat : gen -> int -> int 
end;  (* signature RANDOM *)

The signature is also contained in the file random.sml. Your implementation should satisfy the following specification.

init seed => gen
should return a random number generator gen inititalized by seed. If seed < 0 then init should raise the exception Domain.
randomNat gen bound => n
where n is a random number in the range 0 <= n < bound.

Problem 3: Boggle® Board (25 points)

In this problem you will program the "Big Boggle" equipment, which consist of a 5 by 5 grid and 25 letter cubes. After shaking the grid with the letter cubes under a dome, the cubes fall into the places in the grid and create a 5 by 5 layout of letters. The players then search for words as described in Problem 4.

Create a functor Board which, when given an implementation of dictionaries (satisfying some instance of the DICT signature) and an implementation of a random number generator (satisfying the signature RANDOM) creates a structure satisfying the signature BOARD given below. The signature can also be found in the file board.sml

signature BOARD =
  type board				(* abstract *)
  val foldl : (((int * int) * char) * 'b -> 'b) -> 'b -> board -> 'b
  val lookup : board -> (int * int) -> char option

  val shake : unit -> board
  val show : board -> unit
end;  (* signature BOARD *)
The implementation should satisfy the following specifications.
type board
an abstract type representing a 5 by 5 grid.
foldl f init b => c
works like foldl on tables specified in Problem 1, where instead of a general 'a entry, f is applied to a pair consisting of the address of the grid square and the letter visible at that square.
lookup b (i,j) => SOME(c) or NONE
looks up the character c in place (i,j) and returns NONE if (i,j) lies outside the grid.
shake () => b
returns a new randomly initialized board b which models the process of shaking the grid. Note that successive calls to this function should return different boards.
show b => ()
has the effect of printing the board b in a readable format.
The following are the 6 sides of each of the 25 letter cubes which come with the standard game.
        worvgr    aeaeee    eeeema    oootut    hhrlod  
        qxbzkj    stncec    hdntdo    syripf    towonu  
        magenn    teliic    lrnhod    titeii    geeuma  
        sarafi    ttetmo    tcsiep    elptic    rdhonl  
        ssunes    ednann    rrpryi    fasraa    fsyrai 
Note that the letter q above is actually shown as Qu on the cube. This should be taken into account when printing a board.

Problem 4: Boggle® Word Finder (35 points)

In this problem we assume an implementation of a board as specified above and write a search procedure to find all words on a given board longer than a given threshold (in the standard game, this threshold is fixed at 3).

A word may start on any square on the grid and continue with a letter on an adjacent cube (including diagonally adjacent). The word may continue in this way with arbitrary zig-zagging, but no cube may be used more than once. For example, in the situation

      S O
      I L

we can make words SOIL, SILO, OIL, and OILS, but not SOILS (since the first S cannot be reused).

An implementation of a structure IOUtils and functors Read and Wordlist are provided in the file read.sml. The word list in the file ospd3.list is the Official Scrabble Players Dictionary® as used for Scrabble tournament play and contains 172,000 entries, all of which are reported to be English words (excluding proper names, see the Scrabble FAQ for more information). The word list created by the function in Read is represented as a trie with keys as character lists and unit as the type of the data entries. This is because we are only interested in storing valid words, but not in associating any information with the words.

Implement the search for all words on a Boggle board as a functor Boggle which takes an implementation Board' of a board and Wordlist containing a word list, returning a structure satisfying the signature below. The signature can also be found in the file boggle.sml.

signature BOGGLE =
  structure Board : BOARD
  val words : Board.board * int -> int
end;  (* signature BOGGLE *)
This must satisfy the following specification.
structure Board
An implementation of the board as specified in Problem 3.
words (b, n) => k
returns the number k of words of length greater or equal to n in board b (you may assume n greater than 1). As an effect, this function must also print all words found, without duplication, in alphabetical order. Note that Qu counts as two letters, even though they appear together on one side of one of the cubes (and therefore always have to be used together).

Hint: Implement the main search as a function with a failure continuation which accepts and returns a trie of all words found so far.

Since we will be applying your Boggle functor to our own board configurations for testing purposes, the functor must take two structure arguments with the names specified below
functor Boggle
  (structure Board' : BOARD
   structure Wordlist : WORDLIST)
  :> ... =

Problem 5: Putting Things Together (5 points)

Now you need to apply the functors (either provided or programmed yourself) to create the structures which allow you to play the game. Create the following structures and any auxiliary structures you might need.

structure CharOrder :> ORDER where type key1 = char
structure Trie :> TRIE where type key1 = char
structure Read :> READ where type 'a Dict.dict = 'a Trie.dict
structure OSPD3 :> WORDLIST where type 'a Trie.dict = 'a Trie.dict
structure Board :> BOARD
structure Boggle :> BOGGLE where type Board.board = Board.board

Hint: In order to create the Board structure you need a "dictionary" mapping grid squares to characters. Use your Trie functor over a different key1 type.

Problem 6: Interactive Game Playing (20 points Extra Credit)

Extend the signature BOGGLE and its implementation to allow an interactive game. The function play : int -> int * int should shake the board, show it, and then read lines from standard input (see library structure TextIO). Each word should be checked, rejecting illegal words. When input is terminated by a line containing only a period "." the computer shows the words that were missed and gives the percentage of words found.

The argument to play is the minimum word length considered, and the result is a pair consisting of the number of words on the board and the number of words found by the human player.

Handin instructions

Put your SML code into a single file named ass4.sml in your ass4 handin directory. The file as handed in should not repeat the provided signatures or redeclare them. Please keep a backup for your records.

Your handin file for this assignment is
/afs/andrew/scs/cs/15-212-ML/studentdir/<your andrew id>/ass4/ass4.sml