1 {-# LANGUAGE ScopedTypeVariables #-}
2 module Hjugement.Section where
4 import Control.Applicative (Applicative(..))
6 import Data.Either (Either(..))
7 import Data.Eq (Eq(..))
8 import Data.Foldable (Foldable(..), any)
9 import Data.Function (($), (.))
10 import Data.Functor ((<$>), (<$))
11 import Data.Hashable (Hashable(..))
12 import Data.Maybe (Maybe(..), isNothing, maybe, fromMaybe)
13 import Data.Ord (Ord(..))
14 import Data.Traversable (Traversable(..))
16 import Prelude (Num(..), Fractional(..), toRational)
17 import Text.Show (Show(..))
18 import qualified Data.HashMap.Strict as HM
19 import qualified Data.HashSet as HS
20 import qualified Data.List as List
21 import qualified Data.Map.Strict as Map
26 -- | An opinion of a 'judge' about a 'choice' at a specific section 'Node'.
29 { sectionShare :: Maybe Share
30 -- ^ A 'Share' within the parent 'Node'
31 -- (defaulting to a 'Share' computed as the remaining 'Share' to reach 1
32 -- divided by the number of defaulted 'Share's).
33 , sectionGrade :: Maybe grade
34 -- ^ A 'grade' attributed to the current 'Node'
35 -- (defaulting to the 'grade' set on an ancestor 'Node' if any,
36 -- or the |judge|'s default grade).
39 -- ** Type 'SectionByJudge'
40 type SectionByJudge judge grade = HM.HashMap judge (Section grade)
42 -- ** Type 'SectionByJudgeByChoice'
43 -- | Node value of a 'Tree' holding a 'Section', per 'judge', per choice.
44 type SectionByJudgeByChoice choice judge grade = HM.HashMap choice (SectionByJudge judge grade)
46 -- * Type 'ErrorSection'
47 data ErrorSection choice judge grade
48 = ErrorSection_unknown_choices (HS.HashSet choice)
49 -- ^ When some 'choice's are not known.
50 | ErrorSection_unknown_judges (HM.HashMap choice (HS.HashSet judge))
51 -- ^ When some 'judge's are not known.
52 | ErrorSection_invalid_shares (HM.HashMap choice (HM.HashMap judge [Share]))
53 -- ^ When at least one of the 'Share's is not positive, or when their sum is not 1.
56 -- | Compute the 'Opinions' of the given |Judges| about the given 'Choices',
57 -- from the 'grade' (specified or omitted) attributed to 'Choice's
58 -- and the 'Share's (specified or omitted) attributed to 'Node'
61 forall choice judge grade.
72 Tree (SectionByJudgeByChoice choice judge grade) ->
73 Either (ErrorSection choice judge grade)
74 (Tree (OpinionsByChoice choice judge grade))
75 opinionsBySection cs js = go ((singleGrade <$> js) <$ HS.toMap cs)
77 go :: HM.HashMap choice (HM.HashMap judge (Distribution grade)) ->
78 Tree (SectionByJudgeByChoice choice judge grade) ->
79 Either (ErrorSection choice judge grade)
80 (Tree (OpinionsByChoice choice judge grade))
81 go defaultDistJC (Node currOpinJC childOpinJCS) =
82 -- From current |Node|'s value.
83 let currDistJC :: HM.HashMap choice (HM.HashMap judge (Distribution grade)) =
84 -- Collect the 'Distribution' of current 'Node',
85 -- and insert default 'Distribution'
86 -- for each unspecified 'judge'
87 -- of each (specified or unspecified) 'choice'.
89 HM.mapWithKey (\choice ->
90 HM.mapWithKey (\judge ->
91 maybe (defaultDistJC HM.!choice HM.!judge) singleGrade .
99 -- From children 'Node's.
100 let defaultChildShareSJC = ([Nothing] <$ js) <$ defaultDistJC in
101 let maybeChildShareSJC :: HM.HashMap choice (HM.HashMap judge [Maybe Share]) =
102 -- Collect the (specified or explicitely (with 'Nothing') unspecified) 'Share's by section,
103 -- and insert all unspecified 'Share's when a 'choice' or a 'judge' is unspecified.
104 foldr (\childOpinJC ->
105 let specifiedChildShareSJC = (pure . sectionShare <$>) <$> rootLabel childOpinJC in
106 -- Fusion specified 'choice's into accum.
107 HM.unionWith (HM.unionWith (List.++)) $
108 -- Add default 'Share' for this 'Node',
109 -- for each unspecified 'judge' of specified and unspecified 'choice'.
110 HM.unionWith HM.union
111 specifiedChildShareSJC
112 defaultChildShareSJC)
116 let childShareSJC :: HM.HashMap choice (HM.HashMap judge [Share]) =
117 -- Replace unspecified shares of each child 'Node'
118 -- by an even default: the total remaining 'Share'
119 -- divided by the number of unspecified 'Share's.
120 (<$> maybeChildShareSJC) $ \maybeShareSJ ->
121 (<$> maybeShareSJ) $ \maybeShareS ->
122 let specifiedShare = sum $ fromMaybe 0 <$> maybeShareS in
123 let unspecifiedShares = toRational $ List.length $ List.filter isNothing maybeShareS in
124 let defaultShare = (1 - specifiedShare) / unspecifiedShares in
125 fromMaybe defaultShare <$> maybeShareS
128 -- Test for unknown choices.
129 _ | unknownChoices <- currOpinJC`HM.difference`defaultDistJC
130 , not $ null unknownChoices ->
131 Left $ ErrorSection_unknown_choices $
132 HS.fromMap $ (() <$) $ unknownChoices
133 -- Test for unknown judges.
134 _ | unknownJudgesC <- HM.filter (not . null) $
135 HM.intersectionWith HM.difference
138 , not $ null unknownJudgesC ->
139 Left $ ErrorSection_unknown_judges $
140 HS.fromMap . (() <$) <$> unknownJudgesC
141 -- Handle no child 'Node':
142 -- current 'Distribution' is computed from current |Node|'s value ('currOpinJC')
143 -- and inherited default 'Distribution' ('defaultDistJC').
144 [] -> Right $ Node currDistJC []
145 -- Test for invalid shares.
146 _ | invalidSharesJC <-
147 HM.filter (not . null) $
148 HM.filter (\ss -> any (< 0) ss || sum ss /= 1)
150 , not $ null invalidSharesJC ->
151 Left $ ErrorSection_invalid_shares invalidSharesJC
152 -- Handle children 'Node's:
153 -- current 'Opinions' is computed from the 'Opinions' of the children 'Node's.
155 distJCS :: [Tree (HM.HashMap choice (HM.HashMap judge (Distribution grade)))] <-
156 traverse (go $ currDistJC) childOpinJCS
157 -- 'grade's set at current 'Node' ('currDistJC')
158 -- become the new default 'grade's ('defaultDistJC')
159 -- within its children 'Node's.
160 let distSJC :: HM.HashMap choice (HM.HashMap judge [Distribution grade]) =
161 -- Collect the 'Distribution's by section.
163 let newDistSJC = (pure <$>) <$> rootLabel distJC in
164 HM.unionWith (HM.unionWith (List.++)) newDistSJC)
167 let distJC :: HM.HashMap choice (HM.HashMap judge (Distribution grade)) =
168 -- Compute the current 'Distribution' by scaling (share *) and merging (+)
169 -- the children 'Distribution's.
170 HM.mapWithKey (\choice ->
171 HM.mapWithKey (\judge ->
173 List.zipWith (\share dist -> (share *) <$> dist)
174 (childShareSJC HM.!choice HM.!judge)))
176 Right $ Node distJC distJCS