Add possibility of unassigned topics

This commit is contained in:
David Pätzel 2023-04-26 15:47:05 +02:00
parent da5fc31ab8
commit 77d29208d2

View File

@ -1,11 +1,11 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Seminar where module Seminar where
import Data.List ((!!), (\\), lookup, zipWith3) import Data.List (lookup, zipWith3, (!!), (\\))
import Data.List.Extra (delete, nubOrd, nubOrdOn) import Data.List.Extra (delete, nubOrd, nubOrdOn)
import Data.Random import Data.Random
import qualified Data.Text as T import qualified Data.Text as T
@ -23,8 +23,9 @@ type Student = Text
type Topic = Text type Topic = Text
instance Pretty (Maybe Topic) where instance Pretty (Maybe Topic) where
-- instance Pretty (Maybe Student) where
pretty (Just t) = t pretty (Just t) = t
pretty Nothing = "Kein Thema" pretty Nothing = "Kein"
newtype Priorities = P {unP :: [(Student, [(Topic, Int)])]} newtype Priorities = P {unP :: [(Student, [(Topic, Int)])]}
deriving (Eq, Show) deriving (Eq, Show)
@ -32,17 +33,16 @@ newtype Priorities = P {unP :: [(Student, [(Topic, Int)])]}
instance Arbitrary Priorities where instance Arbitrary Priorities where
arbitrary = priorities <$> Test.QuickCheck.arbitrary arbitrary = priorities <$> Test.QuickCheck.arbitrary
{-| -- |
Smart constructor for priority lists. -- Smart constructor for priority lists.
--
Completes a priority list, that is, if students did not assign priorities to -- Completes a priority list, that is, if students did not assign priorities to
certain topics, adds these topics to their respective priority lists having a -- certain topics, adds these topics to their respective priority lists having a
priority of one less than the lowest priority assigned by them. -- priority of one less than the lowest priority assigned by them.
--
In addition, throws out all but the first occurence of each topic in a student's -- In addition, throws out all but the first occurence of each topic in a student's
list (i.e. removes assignments of multiple priorities to one topic for each -- list (i.e. removes assignments of multiple priorities to one topic for each
student) as well as all but the first occurrence of each student. -- student) as well as all but the first occurrence of each student.
-}
priorities :: [(Student, [(Topic, Int)])] -> Priorities priorities :: [(Student, [(Topic, Int)])] -> Priorities
priorities p = P . nubOrdOn fst $ second priorities1 <$> p priorities p = P . nubOrdOn fst $ second priorities1 <$> p
where where
@ -61,25 +61,22 @@ prop_priorities_allListsSameLength p =
[] -> True [] -> True
(s : ss) -> all (((length . snd) s ==) . length . snd) ss (s : ss) -> all (((length . snd) s ==) . length . snd) ss
{-| -- |
The students that assigned priorities to topics. -- The students that assigned priorities to topics.
-}
students :: Priorities -> [Student] students :: Priorities -> [Student]
students = fmap fst . unP students = fmap fst . unP
{-| -- |
The topics students assigned priorities to. -- The topics students assigned priorities to.
--
Since 'Priorities' objects are well-formed due to the smart constructor, we can -- Since 'Priorities' objects are well-formed due to the smart constructor, we can
simply return the topics the first student assigned priorities to. -- simply return the topics the first student assigned priorities to.
-}
topics :: Priorities -> [Topic] topics :: Priorities -> [Topic]
topics (P []) = [] topics (P []) = []
topics (P (s : _)) = fmap fst . snd $ s topics (P (s : _)) = fmap fst . snd $ s
{-| -- |
The priority value given by a student to a topic. -- The priority value given by a student to a topic.
-}
prioOf :: Priorities -> Student -> Topic -> Int prioOf :: Priorities -> Student -> Topic -> Int
prioOf p s t = fromMaybe (lowestPriority p + 1) $ lookup s (unP p) >>= lookup t prioOf p s t = fromMaybe (lowestPriority p + 1) $ lookup s (unP p) >>= lookup t
@ -94,13 +91,12 @@ prop_prioOf_singletonNotFound :: Bool
prop_prioOf_singletonNotFound = prop_prioOf_singletonNotFound =
prioOf (P [("S", [("Existing topic", 10)])]) "S" "Non-existing topic" == 11 prioOf (P [("S", [("Existing topic", 10)])]) "S" "Non-existing topic" == 11
{-| -- |
The lowest priority assigned by a student to a topic. -- The lowest priority assigned by a student to a topic.
-}
lowestPriority :: Priorities -> Int lowestPriority :: Priorities -> Int
lowestPriority = fromMaybe 0 . maximumMay . fmap snd . join . fmap snd . unP lowestPriority = fromMaybe 0 . maximumMay . fmap snd . join . fmap snd . unP
type Assignment = [(Student, Maybe Topic)] type Assignment = [(Maybe Student, Maybe Topic)]
data I = I Priorities Assignment data I = I Priorities Assignment
deriving (Eq, Show) deriving (Eq, Show)
@ -109,43 +105,46 @@ instance Pretty I where
pretty (I p a) = pretty (I p a) =
T.unlines (gene <$> a) T.unlines (gene <$> a)
where where
gene :: (Student, Maybe Topic) -> Text gene :: (Maybe Student, Maybe Topic) -> Text
gene (s, t) = gene (s, t) =
pretty s <> ": " <> pretty t <> prio s t pretty s <> ": " <> pretty t <> prio s t
prio :: Student -> Maybe Topic -> Text prio :: Maybe Student -> Maybe Topic -> Text
prio s t = " (" <> show (prioOf' p s t) <> ")" prio s t = " (" <> show (prioOf' p s t) <> ")"
{-| -- |
The priority value given by a student to a topic including the case of her not -- The priority value given by a student to a topic including the case of her not
receiving a topic. -- receiving a topic.
-} prioOf' :: Priorities -> Maybe Student -> Maybe Topic -> Int
prioOf' :: Priorities -> Student -> Maybe Topic -> Int -- TODO Maybe make this neutral?
prioOf' p _ Nothing = lowestPriority p + 2 prioOf' p Nothing Nothing = lowestPriority p + 2
prioOf' p s (Just t) = prioOf p s t prioOf' p (Just s) Nothing = lowestPriority p + 2
prioOf' p Nothing (Just t) = lowestPriority p + 2
prioOf' p (Just s) (Just t) = prioOf p s t
instance Individual I where instance Individual I where
new (I p _) = new (I p _) =
sample $ I p . zip (nubOrd $ students p) <$> shuffle topics' sample $ I p . zip students' <$> shuffle topics'
where where
topics' = (Just <$> topics p) ++ padding topics' = (Just <$> topics p) ++ tPadding
padding = replicate (length (students p) - length (topics p)) Nothing tPadding = replicate (length (students p) - length (topics p)) Nothing
students' = (Just <$> students p) ++ sPadding
sPadding = replicate (length (topics p) - length (students p)) Nothing
fitness (I p a) = fitness (I p a) =
return . negate . sum return . negate . sum $
$ fromIntegral . uncurry (prioOf' p) <$> a fromIntegral . uncurry (prioOf' p) <$> a
mutate (I p a) = do mutate (I p a) = do
x <- sample $ Uniform 0 (length a - 1) x <- sample $ Uniform 0 (length a - 1)
y <- sample $ Uniform 0 (length a - 1) y <- sample $ Uniform 0 (length a - 1)
return . I p $ switch x y a return . I p $ switch x y a
{-| -- \|
Borrowed from TSP: Crossover cuts the parents in two and swaps them (if this -- Borrowed from TSP: Crossover cuts the parents in two and swaps them (if this
does not create an invalid offspring). -- does not create an invalid offspring).
--
TODO Assumes that both individuals are based on the same priorities. -- TODO Assumes that both individuals are based on the same priorities.
-} --
crossover1 (I p a1) (I _ a2) = do crossover1 (I p a1) (I _ a2) = do
let l = fromIntegral $ min (length a1) (length a2) :: Double let l = fromIntegral $ min (length a1) (length a2) :: Double
x <- sample $ Uniform 0 l x <- sample $ Uniform 0 l
@ -157,9 +156,8 @@ instance Individual I where
where where
f x v1 v2 i = if i <= x then v1 else v2 f x v1 v2 i = if i <= x then v1 else v2
{-| -- |
Swaps topics at positions 'i'' and 'j'' in the given assignment. -- Swaps topics at positions 'i'' and 'j'' in the given assignment.
-}
switch :: Int -> Int -> Assignment -> Assignment switch :: Int -> Int -> Assignment -> Assignment
switch i' j' xs switch i' j' xs
| i' == j' = xs | i' == j' = xs
@ -174,57 +172,54 @@ switch i' j' xs
in left ++ [(fst ei, snd ej)] ++ middle ++ [(fst ej, snd ei)] ++ right in left ++ [(fst ei, snd ej)] ++ middle ++ [(fst ej, snd ei)] ++ right
| otherwise = xs | otherwise = xs
{-| -- |
Whether the given assignment is valid (every student occurs at most once, as -- Whether the given assignment is valid (every student occurs at most once, as
does every topic; also, there is only no topic given to students if there are -- does every topic; also, there is only no topic given to students if there are
less topics than students). -- less topics than students).
--
Assumes that the priorities are well-formed. -- Assumes that the priorities are well-formed.
-}
valid :: Priorities -> Assignment -> Bool valid :: Priorities -> Assignment -> Bool
valid p a = valid p a =
-- all students must be part of the solution -- all students must be part of the solution
sort (students p) == sort studentsAssigned sort (students p) == (catMaybes $ sort studentsAssigned)
-- each actual topic (i.e. not “no topic”) is assigned at most once -- each actual topic (i.e. not “no topic”) is assigned at most once
&& nubOrd (delete Nothing topicsAssigned) == delete Nothing topicsAssigned && nubOrd (delete Nothing topicsAssigned) == delete Nothing topicsAssigned
where where
studentsAssigned = fmap fst a studentsAssigned = fmap fst a
topicsAssigned = fmap snd a topicsAssigned = fmap snd a
prop_new_valid :: Priorities -> Property -- prop_new_valid :: Priorities -> Property
prop_new_valid p = monadicIO $ do -- prop_new_valid p = monadicIO $ do
I _ a <- lift $ new (I p []) -- I _ a <- lift $ new (I p [])
assert $ valid p a -- assert $ valid p a
prop_mutate_valid :: Priorities -> Property -- prop_mutate_valid :: Priorities -> Property
prop_mutate_valid p = monadicIO $ do -- prop_mutate_valid p = monadicIO $ do
a <- lift . new $ I p [] -- a <- lift . new $ I p []
I _ a <- lift $ mutate a -- I _ a <- lift $ mutate a
assert $ valid p a -- assert $ valid p a
prop_crossover1_valid :: Priorities -> Property -- prop_crossover1_valid :: Priorities -> Property
prop_crossover1_valid p = monadicIO $ do -- prop_crossover1_valid p = monadicIO $ do
a1 <- lift . new $ I p [] -- a1 <- lift . new $ I p []
a2 <- lift . new $ I p [] -- a2 <- lift . new $ I p []
asM <- lift $ crossover1 a1 a2 -- asM <- lift $ crossover1 a1 a2
assert -- assert
$ case asM of -- $ case asM of
Just (I _ a1', I _ a2') -> valid p a1' && valid p a2' -- Just (I _ a1', I _ a2') -> valid p a1' && valid p a2'
Nothing -> True -- Nothing -> True
{-| -- |
Generator for lists fulfilling 'unique', that is, only containing unique -- Generator for lists fulfilling 'unique', that is, only containing unique
elements. -- elements.
-}
noDupsList :: (Arbitrary a, Eq a, Ord a) => Gen [a] noDupsList :: (Arbitrary a, Eq a, Ord a) => Gen [a]
noDupsList = nubOrd <$> arbitrary noDupsList = nubOrd <$> arbitrary
prop_noDupsList :: Property prop_noDupsList :: Property
prop_noDupsList = forAll (noDupsList :: Gen [Int]) unique prop_noDupsList = forAll (noDupsList :: Gen [Int]) unique
{-| -- |
Whether the given list only contains unique elements. -- Whether the given list only contains unique elements.
-}
unique :: (Ord a) => [a] -> Bool unique :: (Ord a) => [a] -> Bool
unique xs = length xs == (length . nubOrd) xs unique xs = length xs == (length . nubOrd) xs