summaryrefslogtreecommitdiff
path: root/src/Preferences.hs
blob: b40accb353392efebcfada134a863e1e34c674ee (plain)
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
module Preferences(
    Preference,
    FullPreferences,

    normalise,
    parsePreferences
    ) where




import qualified Candidate as Typ
import qualified Text.ParserCombinators.Parsec as Parsec
import qualified Data.List as List




type Preference = (Typ.Position,Typ.Ranking)
type FullPreferences = ([Preference],[Preference])




minAboveTheLine = 1
minBelowTheLine = 6




--  converts a set of above+below-the-line preferences to just formal below-the-line
normalise :: Typ.AboveLineBallot -> Typ.BelowLineBallot -> FullPreferences -> Maybe [Preference]
normalise a b f =
    let na = extractFormal (fst f)
        nb = extractFormal (snd f)
    in if (isValidFormal minBelowTheLine b nb)
            then Just nb
            else if (isValidFormal minAboveTheLine a na)
                    then Just (fromAboveToBelow a na)
                    else Nothing




extractFormal :: [Preference] -> [Preference]
extractFormal pref =
    let formTail n r p =
            let (matches,rest) = List.partition ((== n) . snd) p
            in if (p == [] || (length matches) /= 1)
                then r
                else formTail (n + 1) ((head matches):r) rest
    in formTail 1 [] pref




isValidFormal :: Foldable t => Int -> t a -> [Preference] -> Bool
isValidFormal minLimit ballot pref =
    (length pref >= minLimit) || (length pref == length ballot)




--  inefficient?
fromAboveToBelow :: Typ.AboveLineBallot -> [Preference] -> [Preference]
fromAboveToBelow a p =
    let sortedByRanking = List.sortBy (\x y -> compare (snd x) (snd y)) p
        tailFunc n bp ap =
            if (ap == [])
                then bp
                else let place = fst (head ap)
                         newPrefs = zip (a !! (place - 1)) [n, n+1 ..]
                     in tailFunc (n + length newPrefs) (bp ++ newPrefs) (tail ap)
    in tailFunc 1 [] sortedByRanking




--  the two int arguments are the number of boxes above the line and the number
--  of boxes below the line respectively
parsePreferences :: Int -> Int -> String -> Either Parsec.ParseError FullPreferences
parsePreferences aboveBoxes belowBoxes input =
    Parsec.parse (preference aboveBoxes belowBoxes) "error" input


preference a b = do
    x <- Parsec.count a rank
    y <- Parsec.count b rank
    Parsec.eof
    let xr = map (read :: String -> Typ.Ranking) x
        yr = map (read :: String -> Typ.Ranking) y
        xp = (filter ((> 0) . snd) (zip [1,2..] xr))
        yp = (filter ((> 0) . snd) (zip [1,2..] yr))
    return (xp,yp)


rank = do
    n <- Parsec.choice [normalRank, weirdRank, nullRank]
    Parsec.choice [Parsec.char ',' >> return (), Parsec.eof]
    return n


normalRank = do
    n <- Parsec.oneOf "123456789"
    ns <- Parsec.many Parsec.digit
    return (n:ns)


--  these symbols are taken to mean '1' according to AEC guidelines
weirdRank = do
    Parsec.choice [Parsec.char '/', Parsec.char '*']
    return "1"


--  these ranks are standins that will be filtered out
nullRank = return "0"