MIFFS Is Fun For Sums
by rich
This manual really is a woeful resource to try to learn (what is essentially) ML from, but if I'm honest, I'm only writing this because I enjoy it. So this section is gratuitously for me to write some fun or useful code and write it up. It may be helpful if you are using MIFFS and want to see what it can do, or more specifically if you're doing a Stats Examples Sheet.
Sorting is great for some really elegant code. I'm told the majority of computer time is spent sorting things, or at least it was from the very earliest days up until fairly recently. As such, a lot of very clever people have spent lots of time devising sorting algorithms that are, mathematically speaking, perfect. Quicksort is not quite it, but if you add some extra logic to catch deliberately nasty cases (such as reverse-sorted data) then can be.
So how does it work? It has most benefits in a language like C (for arrays), but works fine with linked lists. We first need to divide a list about a pivot - into two halves, one half smaller than (or equal to) the pivot, the other half greater than it. (Put MIFFS into multi line mode and make it a little wider if you want to try this.)
fun pivot (a::l) = let
fun part([], bef,aft) = (bef,aft)
| part(x::xs,bef,aft) = if x <= a then part(xs,x::bef,aft)
else part(xs,bef,x::aft)
val (p,q) = part(l,[],[])
in
(p, a, q)
end
val pivot = fn : α -> α
pivot [0,-1,1,1,-1,-1,1]
val ans = ([-1, -1, -1], 0, [1, 1, 1]) : int list * int * int list
This function uses the first element of the list as the pivot - faster versions sometimes use the median of the first, last and middle element. Try it on some (non-empty) lists if you like.
All that remains is to use the power of recursion - just as you might use mathematical induction.
We can sort an empty list and we can
sort a list of length 1 - so we have our base cases. The inductive step
is to split a large list into two (using pivot) and
recursively sort the pieces, then join them up:
fun qsort [] = []
| qsort [x] = [x]
| qsort list = let
val (p, a, q) = pivot list
in
(qsort p) @ (a :: (qsort q))
end
val qsort = fn : α -> α
Try it out!
I used MIFFS to do an analysis of variance on my stats sheet and thought it might be a nice example. I won't bother to make up new numbers, so you can check my working if you did the same sheet!
The data are four groups of samples: lets get them and some basic stats functions into MIFFS:
val data = [[20.8,20.9,20.7,20.6,20.7],
[20.6,20.3,20.2,20.3,20.5],
[20.9,20.8,20.8,20.7],
[20.8,20.9,20.9,20.4]]
val data = [ ... ] : (real list) list
fun sum [] = 0 | sum (x::xs) = x + sum xs
val sum = fn : α -> α
fun mean list = sum list / length list
val mean = fn : α -> α
fun sxx list = let
val xbar = mean list
in sum (map (fn x => (x-xbar)^2) list) end
val sxx = fn : α -> α
Now we want to calculate the total mean, and the mean of each group:
val totmean = sum (map sum data) / sum (map length data)
val totmean = 20.655555555555555 : real
val samplemeans = map mean data
val samplemeans = [20.740000000000002, 20.380000000000003,
20.799999999999997, 20.75] : real list
We need the between groups variation, defined as the sum over the groups of the size of each group times the square of the difference between the group mean and the total mean. We also want the within group variation, which is the sum over all samples of the square of the difference between the sample and that sample's group mean. The total variation would also help - that's the squared deviation of the entire set.
val betweenvar = let
fun inner([],[]) = 0
| inner(n::ns,mean::means) = n*(mean-totmean)^2 + inner(ns,means)
in inner(map length data, map mean data) end
val betweenvar = 0.5344444444444357 : real
val withinvar = let
fun inner([],[]) = []
| inner(mean::means,list::lists) =
(map (fn x=>(x-mean)^2) list) :: inner(means,lists)
in sum (map sum (inner(map mean data, data))) end
val withinvar = 0.35000000000000003 : real
val totalvar = sum (map sum
(map
(map (fn x=>(x-totmean)^2))
data))
val totalvar = 0.8844444444444424 : real
Ok, these are moderately complex functions, and it might even have been quicker to do it all manually (although in this case I would argue not), but this way is so much more fun ;-)
After all that, let's have another elegant recursive sorting function. First we need to split a list into two:
fun split([], a, b) = (a, b)
| split([x], a, b) = (x::a, b)
| split(x::y::list, a, b) = split(list, x::a, y::b)
val split = fn : α -> α
Now we need to merge two already sorted lists:
fun merge([], ys) = ys
| merge(xs, []) = xs
| merge(x::xs, y::ys) = if x <= y then x :: merge(xs,y::ys)
else y :: merge(x::xs,ys)
val merge = fn : α -> α
Now we use induction again: sort trivial lists trivially, and sort big lists by splitting them - inductively sort the parts - and merge them back together.
fun mergesort [] = []
| mergesort [x] = [x]
| mergesort list = let
val (a,b) = split(list, [], [])
in merge(mergesort a, mergesort b) end
val mergesort = fn : α -> α