hjugement-2.0.0.20180903: Majority/Value.hs
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Majority.Value where
import Data.Bool
import Data.Eq (Eq(..))
import Data.Function (($), (.), on)
import Data.Functor ((<$>))
import Data.List as List
import Data.Maybe (Maybe(..), listToMaybe)
import Data.Ord (Ord(..), Ordering(..), Down(..))
import Data.Ratio ((%))
import Data.Semigroup (Semigroup(..))
import Data.Tuple (snd)
import Prelude (Num(..))
import Text.Show (Show(..))
import qualified Data.HashMap.Strict as HM
import qualified Data.Map.Strict as Map
import Majority.Merit
-- * Type 'MajorityValue'
-- | A 'MajorityValue' is a list of 'grade's
-- made from the successive lower middlemosts of a 'Merit',
-- i.e. from the most consensual 'majorityGrade' to the least.
newtype MajorityValue grade = MajorityValue { unMajorityValue :: [Middle grade] }
deriving (Eq, Show)
instance Ord grade => Ord (MajorityValue grade) where
MajorityValue []`compare`MajorityValue [] = EQ
MajorityValue []`compare`MajorityValue ys | all ((==0) . middleShare) ys = EQ
| otherwise = LT
MajorityValue xs`compare`MajorityValue [] | all ((==0) . middleShare) xs = EQ
| otherwise = GT
mx@(MajorityValue (x:xs)) `compare` my@(MajorityValue (y:ys))
| middleShare x <= 0 && middleShare y <= 0 = MajorityValue xs`compare`MajorityValue ys
| middleShare x <= 0 = MajorityValue xs`compare`my
| middleShare y <= 0 = mx`compare`MajorityValue ys
| otherwise =
lowGrade x`compare`lowGrade y <>
highGrade x`compare`highGrade y <>
case middleShare x`compare`middleShare y of
LT -> compare (MajorityValue xs) (MajorityValue (y{middleShare = middleShare y - middleShare x} : ys))
EQ -> compare (MajorityValue xs) (MajorityValue ys)
GT -> compare (MajorityValue (x{middleShare = middleShare x - middleShare y} : xs)) (MajorityValue ys)
-- ** Type 'Middle'
-- | A centered middle of a 'Merit'.
-- Needed to handle the 'Fractional' capabilities of a 'Share'.
--
-- By construction in 'majorityValue',
-- 'lowGrade' is always lower or equal to 'highGrade'.
data Middle grade = Middle
{ middleShare :: Share -- ^ the same 'Share' of 'lowGrade' and 'highGrade'.
, lowGrade :: grade
, highGrade :: grade
} deriving (Eq, Ord, Show)
-- | The 'majorityValue' is the list of the 'Middle's of the 'Merit' of a 'choice',
-- from the most consensual to the least.
majorityValue :: Ord grade => Merit grade -> MajorityValue grade
majorityValue (Merit countByGrade) = MajorityValue $ goMiddle 0 [] $ Map.toList countByGrade
where
total = sum countByGrade
middle = (1%2) * total
goMiddle :: Ord grade => Share -> [(grade,Share)] -> [(grade,Share)] -> [Middle grade]
goMiddle prevShare ps next =
case next of
[] -> []
curr@(currGrade,currShare):ns ->
let nextShare = prevShare + currShare in
case nextShare`compare`middle of
LT -> goMiddle nextShare (curr:ps) ns
EQ -> goBorders (curr:ps) ns
GT ->
let lowShare = middle - prevShare in
let highShare = nextShare - middle in
let minShare = min lowShare highShare in
Middle minShare currGrade currGrade :
goBorders
((currGrade, lowShare - minShare) : ps)
((currGrade, highShare - minShare) : ns)
goBorders :: [(grade,Share)] -> [(grade,Share)] -> [Middle grade]
goBorders lows highs =
case (lows,highs) of
((lowGrade,lowShare):ls, (highGrade,highShare):hs)
| lowShare <= 0 -> goBorders ls highs
| highShare <= 0 -> goBorders lows hs
| otherwise ->
let minShare = min lowShare highShare in
Middle minShare lowGrade highGrade :
goBorders
((lowGrade , lowShare - minShare) : ls)
((highGrade, highShare - minShare) : hs)
_ -> []
instance (Show grade, Ord grade) => Ord (Merit grade) where
compare = compare `on` majorityValue
-- | The 'majorityGrade' is the lower middlemost
-- (also known as median by experts) of the 'grade's
-- given to a 'choice' by the 'Judges'.
--
-- It is the highest 'grade' approved by an absolute majority of the 'Judges':
-- more than 50% of the 'Judges' give the 'choice' at least a 'grade' of 'majorityGrade',
-- but every 'grade' lower than 'majorityGrade' is rejected by an absolute majority
-- Thus the 'majorityGrade' of a 'choice'
-- is the final 'grade' wished by the majority.
--
-- The 'majorityGrade' is necessarily a word that belongs to 'grades',
-- and it has an absolute meaning.
--
-- When the number of 'Judges' is even, there is a middle-interval
-- (which can, of course, be reduced to a single 'grade'
-- if the two middle 'grade's are the same),
-- then the 'majorityGrade' is the lowest 'grade' of the middle-interval
-- (the “lower middlemost” when there are two in the middle),
-- which is the only one which respects consensus:
-- any other 'choice' whose grades are all within this middle-interval,
-- has a 'majorityGrade' which is greater or equal to this lower middlemost.
majorityGrade :: Show grade => Ord grade => Merit grade -> Maybe grade
majorityGrade m = lowGrade <$> listToMaybe gs where MajorityValue gs = majorityValue m
-- * Type 'MajorityRanking'
type MajorityRanking choice grade = [(choice, MajorityValue grade)]
majorityValueByChoice :: Show grade => Ord grade => MeritByChoice choice grade -> HM.HashMap choice (MajorityValue grade)
majorityValueByChoice (MeritByChoice ms) = majorityValue <$> ms
-- | The 'majorityRanking' ranks all the 'choice's on the basis of their 'grade's.
--
-- Choice A ranks higher than 'choice' B in the 'majorityRanking'
-- if and only if A’s 'majorityValue' is lexicographically above B’s.
-- There can be no tie unless two 'choice's have precisely the same 'majorityValue's.
majorityRanking :: Show grade => Ord grade => MeritByChoice choice grade -> MajorityRanking choice grade
majorityRanking = List.sortOn (Down . snd) . HM.toList . majorityValueByChoice