/
Lib.hs
76 lines (65 loc) · 3.13 KB
/
Lib.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
module Lib
(
Ballot(..),
Candidate(..),
Winner(..),
tally,
) where
import Data.List
import Data.Ord
newtype Candidate = Candidate {name :: String} deriving (Show, Eq)
newtype Ballot = Ballot { votes :: [Candidate] }
data Winner = Winner { candidate :: Candidate } | Tie deriving (Show, Eq) -- TODO: implement n-way Tie
type Election = [Ballot]
type CandidateTally = (Candidate, Int)
type ElectionRound = [CandidateTally]
type FinalElectionResult = (Winner, [ElectionRound])
tally :: Election -> FinalElectionResult
tally [] = (Tie, [])
tally e
| leaderVotes == sndVotes = (Tie, completedTabulation)
| otherwise = (Winner{candidate=leaderCd}, completedTabulation)
where
completedTabulation = tallyRound e []
finalRoundResults = head completedTabulation
(leaderCd, leaderVotes):(sndCd, sndVotes):_ = finalRoundResults
tallyRound :: [Ballot] -> [ElectionRound] -> [ElectionRound]
tallyRound e priorRounds
| leaderVotes >= requiredForMajorityCount = sortedResults:priorRounds
| otherwise = tallyRound redistributedBallots (sortedResults:priorRounds)
where
talliedResults = countBallots e []
sortedResults = sortOn (Data.Ord.Down . latestRoundVoteCount) talliedResults
totalVotes = sum (map latestRoundVoteCount sortedResults)
requiredForMajorityCount = ceiling (fromIntegral totalVotes * 0.5)
(leader,_) = head sortedResults
leaderVotes = latestRoundVoteCount (head sortedResults)
((worstLoser,_):losers) = reverse (tail sortedResults)
worstLoserBallots = filter (isVoteForCandidate worstLoser) e
worstLoserRemovedBallots = filter (not . isVoteForCandidate worstLoser) e
loserEliminatedBallots = eliminateLoserVotes worstLoser worstLoserBallots
redistributedBallots = loserEliminatedBallots ++ worstLoserRemovedBallots
countBallots :: Election -> ElectionRound -> ElectionRound
countBallots [] runningTally = runningTally
countBallots (Ballot{votes=(firstCandidateVote:_)}:ballots) runningTally =
case findCandidateResult firstCandidateVote runningTally of
Nothing -> countBallots ballots ((firstCandidateVote, 1):filteredRunningTally)
Just (firstCandidateVote, votes) -> countBallots ballots ((firstCandidateVote, votes+1):filteredRunningTally)
where
filteredRunningTally = filter (not . isCandidateTally firstCandidateVote) runningTally
findCandidateResult :: Candidate -> ElectionRound -> Maybe CandidateTally
findCandidateResult candidate = find (isCandidateTally candidate)
isCandidateTally :: Candidate -> CandidateTally -> Bool
isCandidateTally soughtCandidate (candidate, _) = soughtCandidate == candidate
latestRoundVoteCount :: CandidateTally -> Int
latestRoundVoteCount (_, count) = count
isVoteForCandidate :: Candidate -> Ballot -> Bool
isVoteForCandidate candidate ballot = head (votes ballot) == candidate
eliminateLoserVotes :: Candidate -> [Ballot] -> [Ballot]
eliminateLoserVotes loser loserBallots =
nullBallotsRemoved
where
loserRemoved = map eliminateHighestVote loserBallots
nullBallotsRemoved = filter (not . null . votes) loserRemoved
eliminateHighestVote :: Ballot -> Ballot
eliminateHighestVote Ballot{votes=(_:remaining)} = Ballot{votes=remaining}