CS 313 - Homework 8 - ML

Due: Wednesday 4/26 at 11:15am

This assignment consists of several programming problems using the language ML. The assignment has two parts; the first part you have to solve individually, but on the second part you can work in groups of two if you want. Please submit your code for this homework as a single file hw8.ml containing all your code. For part two, if you work on a team, only one person should have the code in their hw8.ml file, the other person should just have a comment indicating the name of the team member submitting the code.

Include sample output! You don't have to write special test routines, but you should include as a comment in your file at least two calls to each function you are asked to write. See the file sample.ml in ~schar/cs313/ml/ for an example. Be creative - use your own test cases rather than the ones shown below.

Upload your file via the HW 8 submission page by the beginning of class on Wednesday.

ML on the lab machines

An interpreter for SML/NJ (Standard ML of New Jersey) called "sml" is installed on the lab machines. To run it, simply type "sml", or better yet, "rlwrap sml":
abe:~% rlwrap sml
Standard ML of New Jersey v110.80 [built: Mon Feb  6 17:36:11 2017]
-
At the prompt you can enter ML expressions, terminated by a semicolon. If your expression extends over several lines, the prompt "-" will change to "=".
- val x = 3;
val x = 3 : int
- fun length [] = 0
= |   length (a::y) = 1 + length y;
val length = fn : 'a list -> int
- length [2, 4, 6, 8];
val it = 4 : int
You can load a file with the command "use":
- use "sample.ml";
[opening sample.ml]
val sumlist1 = fn : int list -> int
GC #0.0.0.0.1.12:   (10 ms)
val sumlist2 = fn : int list -> int
val sumlist3 = fn : int list -> int
val sumlist4 = fn : int list -> int
datatype bst = Empty | Node of int * bst * bst
val leaf = fn : int -> bst
val height = fn : bst -> int
val it = () : unit
- 
To exit, type control-D.


Part 1 - to be done individually

  1. Write a function fromToBy(from, to, by) that returns an ascending list of integers as illustrated by these examples:
    - fromToBy(10, 20, 1);
    val it = [10,11,12,13,14,15,16,17,18,19,20] : int list
    
    - fromToBy(1, 20, 3);
    val it = [1,4,7,10,13,16,19] : int list
    
  2. Write tail-recursive versions of the factorial and fibonacci functions. Sample output:
     - fact 7;
     val it = 5040 : int
    
    - map fact (fromToBy(0, 6, 1));
    val it = [1,1,2,6,24,120,720] : int list
      
     - fib 7;
     val it = 13 : int
    
    - map fib (fromToBy(0, 10, 1));
    val it = [0,1,1,2,3,5,8,13,21,34,55] : int list
    
  3. (Ullman, problems 3.3.12, 3.3.13) Write a function subsets(L) that takes a list L, and returns the list of all subsets of L (i.e., the power set of L). To make your job easier, write a function mapcons(n, L), which, given a list L consisting of lists, adds n to the beginning of every list in L (you can define mapcons using map).

    Sample output:

    - mapcons(3, [[2], [4, 5], [], [6, 6, 6]]);
    val it = [[3,2],[3,4,5],[3],[3,6,6,6]] : int list list
    
    - subsets [];
    stdIn:35.1-35.11 Warning: type vars not generalized because of
       value restriction are instantiated to dummy types (X1,X2,...)
    val it = [[]] : ?.X1 list list
    - subsets ([] : int list);
    val it = [[]] : int list list
    - subsets [1];
    val it = [[1],[]] : int list list
    - subsets ["a", "b"];
    val it = [["a","b"],["a"],["b"],[]] : string list list
    - subsets [1, 2, 3];
    val it = [[1,2,3],[1,2],[1,3],[1],[2,3],[2],[3],[]] : int list list
    
    

Part 2 - can be done in pairs

  1. Write a function that computes the "shuffle period" for an even number N, that is, the number of perfect shuffles necessary to put a deck of N cards back into its original order. We will represent a (sorted) deck of N cards using the int list [1, 2, 3, 4, ..., N]. A perfect shuffle splits the deck in two equal halves, and interleaves the cards one-by-one, leaving the first card at the front of the deck. For example, starting with
    [1, 2, 3, 4, 5, 6, 7, 8],
    
    a perfect shuffle yields
    [1, 5, 2, 6, 3, 7, 4, 8].
    
    A second perfect shuffle yields
    [1, 3, 5, 7, 2, 4, 6, 8],
    
    and a third perfect shuffle restores the original order:
    [1, 2, 3, 4, 5, 6, 7, 8].
    
    Therefore, the shuffle period of 8 is 3.

    Implement the following functions:

    Note: To compute the shuffle period, it doesn't matter whether you perform perfect shuffles, or the inverse of perfect shuffles. The inverse of a perfect shuffle can be obtained by collecting all cards in odd positions, followed by all cards in even positions. For example, starting again with
    [1, 2, 3, 4, 5, 6, 7, 8]
    
    an inverse perfect shuffle yields
    [1, 3, 5, 7, 2, 4, 6, 8].
    
    For this problem, you can implement either of the two operations.

    Sample output:

    - idlist 6;
    val it = [1,2,3,4,5,6] : int list
    
    - shuffle(idlist 6);  (* regular shuffle *)
    val it = [1,4,2,5,3,6] : int list
    
    - shuffle2(idlist 6); (* inverse shuffle *)
    val it = [1,3,5,2,4,6] : int list
    
    - shuffleperiod 8;
    val it = 3 : int
    
    - map shuffleperiod (fromToBy(2, 20, 2));
    val it = [1,2,4,3,6,10,12,4,8,18] : int list
    
    
  2. Implement binary search trees in ML.

    The goal of this last problem is to implement general (polymorphic) BSTs that can hold values of any type and support user-defined comparison operations. I advise you, however, to proceed in two steps as outlined below, and start by implementing the good old integer BSTs. You only need to hand in code and sample output of your final code, but you should save a backup copy of your working code for step 1 in a comment before moving on! (ML's comments, handily, can be nested.)

    Step 1: Use the datatype definition from the file sample.ml:

    (* type definition *)
    datatype bst = Empty
                 | Node of int * bst * bst;
    
    (* useful function to create a leaf node *)
    fun leaf n = Node(n, Empty, Empty);
    
    Implement the following functions:

    Sample output:

    - val t = Node(3, Node(1, Empty, leaf(2)), leaf(4));
    val t = Node (3,Node (1,Empty,Node #),Node (4,Empty,Empty)) : bst
      
    - map (fn x => member(x, t)) [0, 3, 5];
    val it = [false,true,false] : bool list
      
    - insert(5, leaf(2));
    val it = Node (2,Empty,Node (5,Empty,Empty)) : bst
      
    - insertList [2, 5];
    val it = Node (5,Node (2,Empty,Empty),Empty) : bst
    
    - inOrder(t);
    val it = [1,2,3,4] : int list
      
    - treeSort [3, 2, 6, 4, 1, 4, 5];
    val it = [1,2,3,4,4,5,6] : int list
    
    Step 2: First, generalize your datatype definition to handle arbitrary types. Sample output after this change:
    - leaf("abc");
    val it = Node ("abc",Empty,Empty) : string bst
    
    - Node(3.5, leaf(0.3), Empty);
    val it = Node (3.5,Node (0.3,Empty,Empty),Empty) : real bst
    
    After you have made this change you will notice that ML still thinks that functions insert, member, etc. operate on an int bst rather than the desired 'a bst. The reason is that these functions use comparisons like "<", whose types default to "int * int -> bool".

    For full generality, the comparison function needs to be passed to member, insert, insertList, and treeSort as an extra parameter. Rewrite these functions appropriately. Sample output (note that "op <" turns the infix operator "<" into a function):

    - insert("b", leaf("a"), op <);
    val it = Node ("a",Empty,Node ("b",Empty,Empty)) : string bst
    
    - val t = insertList([0.6, ~1.0, 0.2], op <);
    val t = Node (0.2,Node (~1.0,Empty,Empty),Node (0.6,Empty,Empty)) : real bst
    
    - member(0.6, t, op <);
    val it = true : bool
    
    - treeSort([2,3,6,~2,~5,0,7,3,~2], op <);
    val it = [~5,~2,~2,0,2,3,3,6,7] : int list
    
    -  treeSort([2,3,6,~2,~5,0,7,3,~2], op >);
    val it = [7,6,3,3,2,0,~2,~2,~5] : int list
    
    -  treeSort([2,3,6,~2,~5,0,7,3,~2], fn(a, b) => abs a < abs b);
    val it = [0,2,~2,~2,3,3,~5,6,7] : int list