CS 313 - Homework 8 - ML

Due: Friday 11/19 at 10am

This assignment consists of several programming problems using the language ML. Installation instructions for SML/NJ are here.

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.sml containing all your code. For part two, if you work on a team, only one person should have the code in their hw8.sml 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.sml 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 10am on Friday.


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. Write a function subsets(x) that takes a list x, and returns the list of all subsets of x (i.e., the power set of x). To make your job easier, write a function mapcons(n, x), which, given a list x consisting of lists, adds n to the beginning of every list in x (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
    
    The order of the subsets within the list does not matter.

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:

    • idlist(n) should return the identity list of n integers [1, 2, ..., n].
    • shuffle(x) should take a list of integers and perform a perfect shuffle, returning a new list. You can assume the number of elements in the list is even.
    • shuffleperiod(n) should compute the shuffle period of n by starting with the identity list and counting how many shuffles are required to restore the original list. You can compare two lists using the "=" operator.
    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. (You don't have to implement both!)

    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! (Recall that ML's comments 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:
    • member(n, t) tests whether int n is in bst t
    • insert(n, t) returns a new tree resulting from inserting n into t (duplicate values should be inserted into the left subtree)
    • insertList(x) returns a new tree resulting from inserting all elements of int list x (in any order) into the empty tree.
    • inOrder(t) returns a list of integers collected using an in-order traversal of t.
    • treeSort(x) sorts int list x using a BST.

    Hint: Check out the function "height" in sample.sml as an example

    Sample output:

    - val t = Node(3, Node(1, Empty, leaf(2)), leaf(4));
    val t = Node (3,Node (1,Empty,Node (2,Empty,Empty)),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
    
    Note: If you print deeply nested structures, ML might abbreviate the output by printing hashmarks ###. There is nothing wrong with that, it's just ML's way of writing "...".

    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
    
    Hint: Check out how we generalized intlist to 'a lst in lecture 25 on datatypes (see ML log).

    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
    

    More hints:

    • Given that you pass in only one comparison operator (e.g. <), you can no longer use >. But note that instead of x > y you can say y < x.
    • Checking for equality using = can also get you into trouble since that's not allowed for reals. Hint: First check < and >, then = remains for the else case.