module Hjugement.Majority where
import Data.Function (on)
import Data.List
import Data.Map.Strict (Map)
import Data.Maybe (fromMaybe)
import Data.Ord (Down(..))
import Data.Semigroup (Semigroup(..))
import Data.Set (Set)
import GHC.Exts (IsList(..))
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
type Choices prop = Set prop
choices :: (Bounded prop , Enum prop , Ord prop) => Choices prop
choices = Set.fromList (enumFrom minBound)
data Scale grade
= Scale
{ scaleGrades :: Set grade
, scaleDefault :: grade
} deriving (Eq, Show)
scale :: (Bounded grade, Enum grade, Ord grade) => Scale grade
scale = Scale { scaleGrades = Set.fromList (enumFrom minBound)
, scaleDefault = toEnum 0
}
scaleOfList :: Eq a => [a] -> a -> Scale Int
scaleOfList gs dg = Scale is di
where
is = fromList $ findIndices (const True) gs
di = fromMaybe (error "default grade not in the scale") $ dg`elemIndex`gs
gradeOfScale :: [a] -> Int -> a
gradeOfScale = (!!)
type Jury judge = Set judge
jury :: (Bounded judge , Enum judge , Ord judge) => Jury judge
jury = Set.fromList (enumFrom minBound)
type Opinion prop grade = Map prop grade
opinion :: (Enum prop, Bounded prop, Ord prop) =>
judge -> [grade] ->
(judge, Opinion prop grade)
opinion j gs = (j, Map.fromList (zip (enumFrom minBound) gs))
newtype Opinions prop grade judge = Opinions (Map judge (Opinion prop grade))
deriving (Eq, Show)
instance (Ord judge, Show judge) => IsList (Opinions prop grade judge) where
type Item (Opinions prop grade judge) = (judge, Opinion prop grade)
fromList = Opinions . Map.fromListWithKey
(\k _x _y -> error $ "duplicate opinion for judge: " <> show k)
toList (Opinions os) = toList os
newtype Merit grade = Merit (Map grade Count)
deriving (Eq, Show)
type Count = Int
instance Ord grade => Semigroup (Merit grade) where
Merit x <> Merit y = Merit (Map.unionWith (+) x y)
instance Ord grade => Ord (Merit grade) where
compare = compare `on` majorityValue
instance (Ord grade, Show grade) => IsList (Merit grade) where
type Item (Merit grade) = (grade, Count)
fromList = Merit . Map.fromListWithKey
(\g _x _y -> error $ "duplicate grade in merit: " <> show g)
toList (Merit cs) = toList cs
merit :: (Ord grade, Ord prop) =>
Scale grade -> prop -> Opinions prop grade judge ->
Merit grade
merit scal prop (Opinions os) = foldr insertOpinion defaultMerit os
where
insertOpinion op (Merit m) = Merit (Map.insertWith (+) g 1 m)
where g = Map.findWithDefault (scaleDefault scal) prop op
defaultMerit = Merit (const 0 `Map.fromSet` scaleGrades scal)
newtype Merits prop grade = Merits (Map prop (Merit grade))
deriving (Eq, Show)
instance (Ord grade, Ord prop) => Semigroup (Merits prop grade) where
Merits x <> Merits y = Merits (Map.unionWith (<>) x y)
instance (Ord prop, Show prop) => IsList (Merits prop grade) where
type Item (Merits prop grade) = (prop, Merit grade)
fromList = Merits . Map.fromListWithKey
(\p _x _y -> error $ "duplicate choice in merits: " <> show p)
toList (Merits cs) = toList cs
merits :: (Ord grade, Ord prop) =>
Scale grade -> Choices prop -> Opinions prop grade judge ->
Merits prop grade
merits scal props (Opinions os) = foldr ((<>) . meritsFromOpinion) defaultMerits os
where
meritsFromOpinion = Merits . (Merit . (`Map.singleton` 1) <$>) . (<> defaultOpinion)
defaultOpinion = const (scaleDefault scal) `Map.fromSet` props
defaultMerits = Merits (const defaultMerit `Map.fromSet` props)
defaultMerit = Merit (const 0 `Map.fromSet` scaleGrades scal)
newtype Value grade = Value [(grade,Count)]
deriving (Eq, Show)
instance Ord grade => Ord (Value grade) where
Value []`compare`Value [] = EQ
Value []`compare`Value ys | all ((==0) . snd) ys = EQ
| otherwise = LT
Value xs`compare`Value [] | all ((==0) . snd) xs = EQ
| otherwise = GT
sx@(Value ((x,cx):xs)) `compare` sy@(Value ((y,cy):ys)) =
case cx`compare`cy of
_ | cx == 0 && cy == 0 -> Value xs`compare`Value ys
_ | cx <= 0 -> Value xs`compare`sy
_ | cy <= 0 -> sx`compare`Value ys
EQ -> x`compare`y <> Value xs`compare`Value ys
LT -> x`compare`y <> Value xs`compare`Value((y,cycx):ys)
GT -> x`compare`y <> Value((x,cxcy):xs)`compare`Value ys
majorityValue :: Ord grade => Merit grade -> Value grade
majorityValue (Merit m) = Value (go m)
where
go gs = case snd (Map.foldlWithKey untilMajGrade (0,[]) gs) of
[] -> []
gw@(g,_):_ -> gw:go (Map.delete g gs)
where
tot = sum gs
untilMajGrade (t,[]) g c | 2*tc >= tot = (tc,[(g,c)])
| otherwise = (tc,[])
where tc = t+c
untilMajGrade acc _g _c = acc
majorityGrade :: Ord grade => Merit grade -> grade
majorityGrade m = fst (head gs) where Value gs = majorityValue m
type Ranking prop = [prop]
majorityRanking :: Ord grade => Merits prop grade -> Ranking prop
majorityRanking = map fst . sortBy (compare `on` Down . snd) . majorityValueByChoice
majorityValueByChoice :: Ord grade => Merits prop grade -> [(prop, Value grade)]
majorityValueByChoice (Merits ms) = Map.toAscList (majorityValue <$> ms)