Merge branch '663-teilnehmerschnitt' into 'master'

Resolve "Teilnehmerschnitt von Lehrveranstaltungen; wie viele Leute sind in allen Veranstaltungen"

Closes #663

See merge request uni2work/uni2work!39
This commit is contained in:
Gregor Kleen 2021-05-28 08:34:57 +00:00
commit c7d08399c3
7 changed files with 91 additions and 29 deletions

View File

@ -6,3 +6,6 @@ ParticipantsIntersectCourses: Kurse
CourseParticipantsRegisteredWithoutField n@Int: #{n} #{pluralDE n "Teilnehmeri:in wurde ohne assoziiertes Studienfach" "Teilnehmer:innen wurden ohne assoziierte Studienfächer"} angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"} CourseParticipantsRegisteredWithoutField n@Int: #{n} #{pluralDE n "Teilnehmeri:in wurde ohne assoziiertes Studienfach" "Teilnehmer:innen wurden ohne assoziierte Studienfächer"} angemeldet, da #{pluralDE n "kein eindeutiges Hauptfach bestimmt werden konnte" "keine eindeutigen Hauptfächer bestimmt werden konnten"}
ParticipantsCsvSheetName tid@TermId ssh@SchoolId: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Kursteilnehmer:innen ParticipantsCsvSheetName tid@TermId ssh@SchoolId: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Kursteilnehmer:innen
CourseParticipants n@Int: Derzeit #{n} angemeldete Kursteilnehmer:innen CourseParticipants n@Int: Derzeit #{n} angemeldete Kursteilnehmer:innen
ParticipantsIntersectNotOne: Schnitt
AllUsersUnion: Vereinigung aller Teilnehmer:innen
AllUsersIntersection: Schnitt aller Teilneher:innen

View File

@ -6,3 +6,6 @@ ParticipantsIntersectCourses: Courses
CourseParticipantsRegisteredWithoutField n: #{n} #{pluralEN n "participant was" "participants were"} registered without #{pluralEN n "an associated field of study" "associated fields of study"}, because #{pluralEN n "it" "they"} could not be determined uniquely. CourseParticipantsRegisteredWithoutField n: #{n} #{pluralEN n "participant was" "participants were"} registered without #{pluralEN n "an associated field of study" "associated fields of study"}, because #{pluralEN n "it" "they"} could not be determined uniquely.
ParticipantsCsvSheetName tid ssh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Participants ParticipantsCsvSheetName tid ssh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Participants
CourseParticipants n: Currently #{n} course #{pluralEN n "participant" "participants"} CourseParticipants n: Currently #{n} course #{pluralEN n "participant" "participants"}
ParticipantsIntersectNotOne: Intersection
AllUsersUnion: Union of all participants
AllUsersIntersection: Intersection of all participants

View File

@ -110,8 +110,10 @@ postParticipantsIntersectR = do
-> Set.size $ Map.findWithDefault Set.empty lCid courseUsers `Set.intersection` Map.findWithDefault Set.empty uCid courseUsers -> Set.size $ Map.findWithDefault Set.empty lCid courseUsers `Set.intersection` Map.findWithDefault Set.empty uCid courseUsers
selfIntersections = Map.mapKeysMonotonic (\cid -> (cid, cid)) $ Set.size <$> courseUsers selfIntersections = Map.mapKeysMonotonic (\cid -> (cid, cid)) $ Set.size <$> courseUsers
intersections' = Map.union intersections selfIntersections intersections' = Map.union intersections selfIntersections
let allUsersUnion = Set.size . Set.unions $ Map.elems courseUsers
return (courses, intersections') let mapIntersect = mapIntersectNotOne courseUsers
let allUsersIntersection = Set.size . setIntersections $ Map.elems courseUsers
return (courses, intersections', mapIntersect, allUsersUnion, allUsersIntersection)
let let
symmIntersection intersections lCid uCid = fromMaybe 0 $ intersections !? (lCid, uCid) <|> intersections !? (uCid, lCid) symmIntersection intersections lCid uCid = fromMaybe 0 $ intersections !? (lCid, uCid) <|> intersections !? (uCid, lCid)

View File

@ -41,6 +41,7 @@ import Utils.HttpConditional as Utils
import Utils.Persist as Utils import Utils.Persist as Utils
import Utils.ARC as Utils import Utils.ARC as Utils
import Utils.LRU as Utils import Utils.LRU as Utils
import Utils.Set as Utils
import Text.Blaze (Markup, ToMarkup(..)) import Text.Blaze (Markup, ToMarkup(..))
@ -562,32 +563,10 @@ withoutSubsequenceBy cmp = go []
| x `cmp` y = go acc a' b | x `cmp` y = go acc a' b
| otherwise = go (y:acc) a b | otherwise = go (y:acc) a b
---------- ----------
-- Sets -- -- Sets --
---------- ----------
-- all functions that used to be here are now in Utils.Set
-- | Intersection of multiple sets. Returns empty set for empty input list
setIntersections :: Ord a => [Set a] -> Set a
setIntersections [] = Set.empty
setIntersections (h:t) = foldl' Set.intersection h t
setMapMaybe :: Ord b => (a -> Maybe b) -> Set a -> Set b
setMapMaybe f = Set.fromList . mapMaybe f . Set.toList
-- | Symmetric difference of two sets.
setSymmDiff :: Ord a => Set a -> Set a -> Set a
setSymmDiff x y = (x `Set.difference` y) `Set.union` (y `Set.difference` x)
setProduct :: Set a -> Set b -> Set (a, b)
-- ^ Depends on the valid internal structure of the given sets
setProduct (Set.toAscList -> as) (Set.toAscList -> bs) = Set.fromDistinctAscList $ (,) <$> as <*> bs
setPartitionEithers :: (Ord a, Ord b) => Set (Either a b) -> (Set a, Set b)
setPartitionEithers = (,) <$> setMapMaybe (preview _Left) <*> setMapMaybe (preview _Right)
setFromFunc :: (Finite k, Ord k) => (k -> Bool) -> Set k
setFromFunc = Set.fromList . flip filter universeF
---------- ----------
-- Maps -- -- Maps --

64
src/Utils/Set.hs Normal file
View File

@ -0,0 +1,64 @@
module Utils.Set
( setIntersectNotOne
, setIntersections
, setMapMaybe
, setSymmDiff
, setProduct
, setPartitionEithers
, setFromFunc
, mapIntersectNotOne
) where
import qualified Data.Set as Set
import qualified Data.Map.Strict()
import qualified Data.Map as Map
import ClassyPrelude
import Data.Universe
import Control.Lens.Prism
import Control.Lens
-- | cardinal number of an intersection of a set and a list of sets
setIntersectNotOne :: Ord a => Set a -> [Set a] -> Int
setIntersectNotOne _ [] = 0
setIntersectNotOne k r = Set.size $ Set.intersection k others where others = Set.unions r
----------------------------------------
-- Functions for Handler.Participants --
----------------------------------------
-- | extracts from a map a list of values (sets) without one specific entry (a)
getAllElemsWithoutOne :: (Ord a) => Map a (Set b) -> a -> [Set b]
getAllElemsWithoutOne m cid = Map.elems $ Map.delete cid m
-- | transforms values (sets) of a map to integers. The number gives information about how many entreis are not only in this one
mapIntersectNotOne :: forall a b. (Ord a, Ord b) => Map a (Set b) -> Map a Int
mapIntersectNotOne m = Map.mapWithKey f m where
f :: a -> Set b -> Int
f k _ = setIntersectNotOne (Map.findWithDefault Set.empty k m) (getAllElemsWithoutOne m k)
--------------------------
-- Functions from Utils --
--------------------------
-- | Intersection of multiple sets. Returns empty set for empty input list
setIntersections :: Ord a => [Set a] -> Set a
setIntersections [] = Set.empty
setIntersections (h:t) = foldl' Set.intersection h t
setMapMaybe :: Ord b => (a -> Maybe b) -> Set a -> Set b
setMapMaybe f = Set.fromList . mapMaybe f . Set.toList
-- | Symmetric difference of two sets.
setSymmDiff :: Ord a => Set a -> Set a -> Set a
setSymmDiff x y = (x `Set.difference` y) `Set.union` (y `Set.difference` x)
setProduct :: Set a -> Set b -> Set (a, b)
-- ^ Depends on the valid internal structure of the given sets
setProduct (Set.toAscList -> as) (Set.toAscList -> bs) = Set.fromDistinctAscList $ (,) <$> as <*> bs
setPartitionEithers :: (Ord a, Ord b) => Set (Either a b) -> (Set a, Set b)
setPartitionEithers = (,) <$> setMapMaybe (preview _Left) <*> setMapMaybe (preview _Right)
setFromFunc :: (Finite k, Ord k) => (k -> Bool) -> Set k
setFromFunc = Set.fromList . flip filter universeF

View File

@ -1,7 +1,7 @@
$newline never $newline never
<section> <section>
^{formWidget} ^{formWidget}
$maybe (courses, intersections) <- intersectionsRes $maybe (courses, intersections, mapIntersect, allUsersUnion, allUsersIntersection) <- intersectionsRes
<section> <section>
<div .scrolltable .scrolltable--bordered> <div .scrolltable .scrolltable--bordered>
<table .table .table--hover .table--condensed> <table .table .table--hover .table--condensed>
@ -11,6 +11,8 @@ $maybe (courses, intersections) <- intersectionsRes
$forall Entity _ Course{courseTerm, courseSchool, courseShorthand} <- courses $forall Entity _ Course{courseTerm, courseSchool, courseShorthand} <- courses
<th .table__th .text--center> <th .table__th .text--center>
#{courseTerm}-#{courseSchool}-#{courseShorthand} #{courseTerm}-#{courseSchool}-#{courseShorthand}
<th .table__th .text--center>
_{MsgParticipantsIntersectNotOne}
<tbody> <tbody>
$forall (l, Entity lCid Course{courseTerm, courseSchool, courseShorthand}) <- lIxed courses $forall (l, Entity lCid Course{courseTerm, courseSchool, courseShorthand}) <- lIxed courses
<tr .table__row> <tr .table__row>
@ -24,3 +26,12 @@ $maybe (courses, intersections) <- intersectionsRes
<td .table__td .text--center :uCid == lCid:.table__td--automatic :uCid /= lCid:.heated :uCid /= lCid:style="--hotness: #{toPathPiece (intersectionHotness intersections lCid uCid)}"> <td .table__td .text--center :uCid == lCid:.table__td--automatic :uCid /= lCid:.heated :uCid /= lCid:style="--hotness: #{toPathPiece (intersectionHotness intersections lCid uCid)}">
$if showNumber n lCid uCid $if showNumber n lCid uCid
#{n} #{n}
$maybe num <- Map.lookup lCid mapIntersect
<td .table__td .text--center .table__td--automatic>
#{num}
<p>
_{MsgAllUsersUnion}: #
#{allUsersUnion}
<p>
_{MsgAllUsersIntersection}: #
#{allUsersIntersection}