GIML: Election Special
British Election Results may be obtained from
ftp://ftp.demon.co.uk/pub/British_Politics
Local copies at /usr/local/etc/ftp/pub/a.cumming/election
This directory include parse.sml and eg.sml which contain function to
read and to examine the data.
Model answers to some coursework questions.
parse.sml
The file
parse.sml
contains several useful functions for reading and
processing the election data. Because the data is slightly inconsistent
in places it is rather more complex than we would prefer.
Here are a few of the functions:
-
flatten, sum, fst, snd, filter, sort and spacestrip have all
been covered elsewhere.
- read takes a file name and returns the following structure:
(string * string list list) list that is a list of constituencies.
Each constituency is represented as a string * (string list list) - the
first string is the name of the constituency - for example
"EDINBURGH, CENTRAL [568]" the list of lists of strings is a list
of contests (usually three contests - one per general election unless there
has been a by-election in that constituency). Each contest is made up of
a list of candidates. The winning candidate (who becomes the MP) is always
listed first.
- party takes a candidate and returns the name of the party
- votes takes a candidate and returns the number of votes
for that candidate
- partyP x takes a candidate and returns true if the candidate
is in party x.
- year takes a contest and returns the year of the contest.
Example
We consider the example given in
eg.sml
Question
Do all the percentages of all the candidates
for all the constituencies fror all the years add to 100?
use "/users/staff/sez125/election/parse.sml";
val allFiles = map (fn s=>"/users/staff/sez125/election/1983."
^s^".txt") ["01","02","03","04","05",
"06","07","08","09","10", "11","12","13","14","15", "16","17"];
We load in the utilities in parse.sml and define a list of all file names,
each file contains roughly 50K giving around 1000K in total. This should
not be a problem for the HP's.
We obtain a list of all constituencies - call them r:
val r = flatten(map read allFiles);
Now extract the list of years for each constituency - this
means ditching the constituency name
map snd r;
Now flatten to obtain a list of all candidates
flatten it;
The function (map perMil) will extract only the percentage
for each set of results, we want to map this over the whole list,
in fact we get 10 times the percentage as the decimal point is ignored
map (map perMil) it;
We now sum each of the lists
map sum it;
Most of these are 1000 - remove them
filter (not o member [1000]) it;
999's and 1001's may be due to rounding errors
filter (not o member [999,1001]) it;
We are left with three rogue results - a little more work is
required to identify them
fun check t = filter (not o (forallP (member [1000,1001,999])) o snd)
(map (appsnd
(map (sum o (map perMil)))) t);
check r;
Question
Which Labour candidate obtained the fewest votes in the
1992 election?
Throw away the constituency name and structure
flatten (map snd r);
Extract the 1992 results
filter (is_yearP "1992") it;
Extract the Labour candidates from the flattened list
filter (partyP "Lab") (flatten it);
sort according to size of vote
sort (fn(a,b)=>votes a > votes b) it;
We want to look at the end of this list
rev it;
Dr. M. Lynda Clarke wins the wooden spoon.
Bug note
There is a "feature" of the Windows version of ml which prevents the
can_input function from returning anything until a read has been attempted.
We can work around this by replacing open_input with a new version.
Simply replace the given definition of read in parse.sml with ...
fun open_in' f = let val tmp = open_in f val w = lookahead tmp
in tmp end;
fun read f = fst(con_file(clean(readall(open_in' f))));
Predicates
A predicate is for our purposes a function which returns true
or false. You may think of a predicate as a question - for this reason
predicates are given names which end in capital P (which looks like
a question mark).
Examples
fun evenP x = (x mod 2 = 0);
This function returns true if the input is even and false otherwise.
fun palindromeP s = (explode s = rev(explode s));
This returns true for palindromic strings.
Combining predicates
When can use functions such as eitherP and forallP to
construct others.
evenP o size is the predicate which asks "does this string contain
an even number of characters?" and so the predicate eitherP (evenP o
size) palindromeP asks "is this string either of even length or a
palindrome?" (Naturally we mean an inclusive or here).
Answer the following:
eitherP (evenP o size) palindromeP "tat";
eitherP (evenP o size) palindromeP "bat";
eitherP (evenP o size) palindromeP "shut";
forallP (eitherP (evenP o size) palindromeP) ["shut","madamimadam","abba"];