Assignment 2

In this assignment we get a bit more practice breaking down a problem into smaller parts and using the various Haskell facilities to do the appropriate transformations.

The problem we will solve is a voting system called Borda count. The situation is as follows:

We will write the code that does this. For example our code will be given an input such as:

allVotes = [
   ["Peter", "Debra", "Oliver", "John"],
   ["Peter", "Oliver", "John", "Debra"],
   ["Oliver", "Debra", "John", "Peter"],
   ["John", "Oliver", "Debra", "Peter"],
   ["Debra", "John", "Oliver", "Peter"]]

getResults allVotes
-- Will produce the string:
-- Oliver             14
-- Debra              13
-- John               12
-- Peter              11

Quick note: To print a string on the interactive console, use putStr or putStrLn, for example:

putStrLn "Hello there!"

Use this only for diagnostic purposes. Your functions should simply be returning strings and not printing directly.

Here is some start code. You may want to add your own tests.

module Voting where

import Test.HUnit
import Data.List (sortBy, nub, sort)

type Candidate = String
type Vote = [Candidate]

allVotes :: [Vote]
allVotes = [
   ["Peter", "Debra", "Oliver", "John"],
   ["Peter", "Oliver", "John", "Debra"],
   ["Oliver", "Debra", "John", "Peter"],
   ["John", "Oliver", "Debra", "Peter"],
   ["Debra", "John", "Oliver", "Peter"]]


-- Function provided for you
getResults :: [Vote] -> IO ()
getResults votes = sequence_ [putStrLn s | s <- formatVotes votes]
   where formatVotes = formatAll . sortedCounts . totalCounts

tests = TestList [
   TestCase $ assertEqual "downToN" [5, 4, 3, 2, 1] (downFromN 5),
   TestCase $ assertEqual "withRanks"
         [("A", 3), ("B", 2), ("C", 1)]
         (withRanks ["A", "B", "C"]),
   TestCase $ assertEqual "allRanks"
         (sort [("A", 3), ("B", 2), ("C", 1), ("B", 3), ("A", 2), ("C", 1)])
         (sort (allRanks [["A", "B", "C"], ["B", "A", "C"]])),
   TestCase $ assertEqual "totalRank"
         5
         (totalRank "B" [("B", 2), ("C", 1), ("B", 3)]),
   TestCase $ assertEqual "allCandidates"
         ["A", "B"]
         (sort (allCandidates [("B", 2), ("B", 3), ("A", 1), ("A", 1)])),
   TestCase $ assertEqual "totalCounts"
         [("A", 5), ("B", 3), ("C", 4)]
         (sort (totalCounts [["A", "B", "C"], ["C", "A", "B"]])),
   TestCase $ assertEqual "cmp1"  EQ (cmp ("A", 2) ("B", 2)),
   TestCase $ assertEqual "cmp2"  LT (cmp ("A", 2) ("B", 1)),
   TestCase $ assertEqual "cmp3"  GT (cmp ("A", 2) ("B", 3)),
   TestCase $ assertEqual "sortedCounts"
         [("A", 5), ("C", 4), ("B", 3)]
         (sortedCounts [("A", 5), ("B", 3), ("C", 4)]),
   TestCase $ assertEqual "neededLength" 5 (neededLength ("Jo", 23)),
   TestCase $ assertEqual "totalNeededLength"
         9
         (totalNeededLength [("Jo", 23), ("Patrick", 4), ("Peter", 123)]),
   TestCase $ assertEqual "formatPair"
         "Jo    123"
         (formatPair 9 ("Jo", 123)),
   TestCase $ assertEqual "formatAll"
         ["Jo   123", "Peter 23"]
         (formatAll [("Jo", 123), ("Peter", 23)])
   ]

Remember to run your tests with:

runTestTT tests

Note that there are two type aliases, one to represent candidates as strings and another to represent a “Vote” as a list of candidates.

Here are the functions you should implement. You should start by specifying their types and a “stub” implementation that does nothing. That way your tests will be runnable.

getResults allVotes