signature MERGESORT = sig
  (* merge_sort(xs) is a list containing the same elements
   * as xs but in ascending (nondescending) sorted order. *)
  val merge_sort: int list -> int list
  (* split(xs) is a pair (ys,zs) where half (rounding up)
   * of the elements of xs are found in ys and the rest
   * are in zs. *)
  val split: int list -> int list * int list
  (* merge(left,right) is a sorted list (in ascending order)
   * containing all the elements of left and right.
   * Requires: left and right are sorted lists *)
  val merge: int list * int list -> int list
end

structure MergeSort :> MERGESORT = struct
  fun split (xs) = let
    fun loop(xs, left, right) =
      (case xs of
         nil => (left, right)
       | x::nil => (x::left, right)
       | x::y::rest => loop(rest, x::left, y::right))
  in
    loop(xs, [], [])
  end

  fun merge (left, right) =
    case (left, right) of
      (nil,_) => right
    | (_,nil) => left
    | (x::left_tail, y::right_tail) =>
        (if x > y then y::(merge(left, right_tail))
                  else x::(merge(left_tail, right)))

  (* Implementation: lists of size 0 or 1 are already sorted.
   * Otherwise, split the list into two lists of equal size,
   * recursively sort them, and then merge the two lists back
   * together. *)
  fun merge_sort (xs) =
    case xs of
      [] => []
    | [x] => [x]
    | _ =>
        let val (left, right) = split xs in
          merge (merge_sort(left), merge_sort(right))
        end

  (* A simpler way to write split. Recall the definition of
   * foldl. What is the asymptotic performance of
   * foldl f lst0 lst where f is an O(1) function and lst
   * is an n-element list? O(n). *)
  fun split2(xs) =
    foldl (fn(x,(left,right)) => (x::right,left)) ([],[]) xs
end

fun sort3(a: int list): int list =
  case a of
    nil => nil
  | [x] => [x]
  | [x,y] => [Int.min(x,y), Int.max(x,y)]
  | a => let
      val n = List.length(a)
      val m = (2*n+2) div 3
      val res1 = sort3(List.take(a, m))
      val res2 = sort3(List.drop(res1, n-m) @
                       List.drop(a, m))
      val res3 = sort3(List.take(res1, n-m) @
                       List.take(res2, 2*m-n))
    in
      res3 @ List.drop(res2,2*m-n)
    end

