2019-10-22 06:49:17 +02:00
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
2019-10-17 19:50:19 +02:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2019-10-18 23:20:19 +02:00
|
|
|
{-# LANGUAGE TemplateHaskell #-}
|
2023-04-26 15:47:05 +02:00
|
|
|
{-# LANGUAGE NoImplicitPrelude #-}
|
2019-10-17 19:50:19 +02:00
|
|
|
|
|
|
|
module Seminar where
|
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
import Data.List (lookup, zipWith3, (!!), (\\))
|
2021-04-27 15:00:44 +02:00
|
|
|
import Data.List.Extra (delete, nubOrd, nubOrdOn)
|
2019-10-17 19:50:19 +02:00
|
|
|
import Data.Random
|
|
|
|
import qualified Data.Text as T
|
|
|
|
import GA
|
|
|
|
import Pretty
|
|
|
|
import Protolude
|
|
|
|
import Test.QuickCheck hiding (sample, shuffle)
|
2019-10-22 06:49:17 +02:00
|
|
|
import Test.QuickCheck.Monadic (assert, monadicIO)
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2019-10-22 06:49:17 +02:00
|
|
|
instance Pretty Text where
|
|
|
|
pretty = identity
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2019-10-22 06:49:17 +02:00
|
|
|
type Student = Text
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2019-10-22 06:49:17 +02:00
|
|
|
type Topic = Text
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2019-10-22 06:49:17 +02:00
|
|
|
instance Pretty (Maybe Topic) where
|
2023-04-26 15:47:05 +02:00
|
|
|
-- instance Pretty (Maybe Student) where
|
2019-10-22 06:49:17 +02:00
|
|
|
pretty (Just t) = t
|
2023-04-26 15:47:05 +02:00
|
|
|
pretty Nothing = "Kein"
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2019-10-22 16:42:16 +02:00
|
|
|
newtype Priorities = P {unP :: [(Student, [(Topic, Int)])]}
|
2019-10-17 19:50:19 +02:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2019-10-22 06:49:17 +02:00
|
|
|
instance Arbitrary Priorities where
|
|
|
|
arbitrary = priorities <$> Test.QuickCheck.arbitrary
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- Smart constructor for priority lists.
|
|
|
|
--
|
|
|
|
-- 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
|
|
|
|
-- 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
|
|
|
|
-- 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.
|
2019-10-22 06:49:17 +02:00
|
|
|
priorities :: [(Student, [(Topic, Int)])] -> Priorities
|
|
|
|
priorities p = P . nubOrdOn fst $ second priorities1 <$> p
|
|
|
|
where
|
|
|
|
priorities1 :: [(Topic, Int)] -> [(Topic, Int)]
|
|
|
|
priorities1 [] =
|
|
|
|
topics p `zip` repeat 1
|
|
|
|
priorities1 ps =
|
|
|
|
let tLacking = topics p \\ (fst <$> ps) :: [Topic]
|
|
|
|
pWorst = maximum (snd <$> ps) + 1 :: Int
|
|
|
|
in nubOrdOn fst $ ps ++ (tLacking `zip` repeat pWorst)
|
|
|
|
topics = nubOrd . concatMap (fmap fst . snd)
|
|
|
|
|
|
|
|
prop_priorities_allListsSameLength :: [(Student, [(Topic, Int)])] -> Bool
|
|
|
|
prop_priorities_allListsSameLength p =
|
|
|
|
case unP . priorities $ p of
|
|
|
|
[] -> True
|
|
|
|
(s : ss) -> all (((length . snd) s ==) . length . snd) ss
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- The students that assigned priorities to topics.
|
2019-10-22 06:49:17 +02:00
|
|
|
students :: Priorities -> [Student]
|
|
|
|
students = fmap fst . unP
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- The topics students assigned priorities to.
|
|
|
|
--
|
|
|
|
-- Since 'Priorities' objects are well-formed due to the smart constructor, we can
|
|
|
|
-- simply return the topics the first student assigned priorities to.
|
2019-10-22 06:49:17 +02:00
|
|
|
topics :: Priorities -> [Topic]
|
|
|
|
topics (P []) = []
|
|
|
|
topics (P (s : _)) = fmap fst . snd $ s
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- The priority value given by a student to a topic.
|
2019-10-22 06:49:17 +02:00
|
|
|
prioOf :: Priorities -> Student -> Topic -> Int
|
|
|
|
prioOf p s t = fromMaybe (lowestPriority p + 1) $ lookup s (unP p) >>= lookup t
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2020-05-02 17:42:04 +02:00
|
|
|
prop_prioOf_empty :: Bool
|
2019-10-22 06:49:17 +02:00
|
|
|
prop_prioOf_empty = prioOf (P []) "S" "T" == 1
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2020-05-02 17:42:04 +02:00
|
|
|
prop_prioOf_singletonFound :: Bool
|
2019-10-22 06:49:17 +02:00
|
|
|
prop_prioOf_singletonFound =
|
|
|
|
prioOf (P [("S", [("Existing topic", 10)])]) "S" "Existing topic" == 10
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2020-05-02 17:42:04 +02:00
|
|
|
prop_prioOf_singletonNotFound :: Bool
|
2019-10-22 06:49:17 +02:00
|
|
|
prop_prioOf_singletonNotFound =
|
|
|
|
prioOf (P [("S", [("Existing topic", 10)])]) "S" "Non-existing topic" == 11
|
2019-10-18 13:47:03 +02:00
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- The lowest priority assigned by a student to a topic.
|
2019-10-22 06:49:17 +02:00
|
|
|
lowestPriority :: Priorities -> Int
|
|
|
|
lowestPriority = fromMaybe 0 . maximumMay . fmap snd . join . fmap snd . unP
|
2019-10-17 19:50:19 +02:00
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
type Assignment = [(Maybe Student, Maybe Topic)]
|
2019-10-17 19:50:19 +02:00
|
|
|
|
|
|
|
data I = I Priorities Assignment
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
instance Pretty I where
|
2020-05-02 17:42:04 +02:00
|
|
|
pretty (I p a) =
|
2019-10-17 19:50:19 +02:00
|
|
|
T.unlines (gene <$> a)
|
|
|
|
where
|
2023-04-26 15:47:05 +02:00
|
|
|
gene :: (Maybe Student, Maybe Topic) -> Text
|
2019-10-17 19:50:19 +02:00
|
|
|
gene (s, t) =
|
|
|
|
pretty s <> ": " <> pretty t <> prio s t
|
2023-04-26 15:47:05 +02:00
|
|
|
prio :: Maybe Student -> Maybe Topic -> Text
|
2019-10-22 06:49:17 +02:00
|
|
|
prio s t = " (" <> show (prioOf' p s t) <> ")"
|
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- The priority value given by a student to a topic including the case of her not
|
|
|
|
-- receiving a topic.
|
|
|
|
prioOf' :: Priorities -> Maybe Student -> Maybe Topic -> Int
|
|
|
|
-- TODO Maybe make this neutral?
|
|
|
|
prioOf' p Nothing Nothing = lowestPriority p + 2
|
|
|
|
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
|
2019-10-17 19:50:19 +02:00
|
|
|
|
|
|
|
instance Individual I where
|
|
|
|
new (I p _) =
|
2024-02-11 21:25:15 +01:00
|
|
|
I p . zip students' <$> shuffle topics'
|
2019-10-22 06:49:17 +02:00
|
|
|
where
|
2023-04-26 15:47:05 +02:00
|
|
|
topics' = (Just <$> topics p) ++ tPadding
|
|
|
|
tPadding = replicate (length (students p) - length (topics p)) Nothing
|
|
|
|
students' = (Just <$> students p) ++ sPadding
|
|
|
|
sPadding = replicate (length (topics p) - length (students p)) Nothing
|
2019-10-17 19:50:19 +02:00
|
|
|
|
|
|
|
fitness (I p a) =
|
2023-04-26 15:47:05 +02:00
|
|
|
return . negate . sum $
|
|
|
|
fromIntegral . uncurry (prioOf' p) <$> a
|
2019-10-17 19:50:19 +02:00
|
|
|
|
|
|
|
mutate (I p a) = do
|
2024-02-11 21:25:15 +01:00
|
|
|
x <- uniform 0 (length a - 1)
|
|
|
|
y <- uniform 0 (length a - 1)
|
2019-10-17 19:50:19 +02:00
|
|
|
return . I p $ switch x y a
|
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- \|
|
|
|
|
-- Borrowed from TSP: Crossover cuts the parents in two and swaps them (if this
|
|
|
|
-- does not create an invalid offspring).
|
|
|
|
--
|
|
|
|
-- TODO Assumes that both individuals are based on the same priorities.
|
|
|
|
--
|
2019-10-17 19:50:19 +02:00
|
|
|
crossover1 (I p a1) (I _ a2) = do
|
|
|
|
let l = fromIntegral $ min (length a1) (length a2) :: Double
|
2024-02-11 21:25:15 +01:00
|
|
|
x <- uniform 0 l
|
2019-10-17 19:50:19 +02:00
|
|
|
let a1' = zipWith3 (f x) a1 a2 [0 ..]
|
|
|
|
let a2' = zipWith3 (f x) a2 a1 [0 ..]
|
2019-10-22 06:49:17 +02:00
|
|
|
if valid p a1' && valid p a2'
|
2019-10-17 19:50:19 +02:00
|
|
|
then return . Just $ (I p a1', I p a2')
|
|
|
|
else return Nothing
|
|
|
|
where
|
|
|
|
f x v1 v2 i = if i <= x then v1 else v2
|
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- Swaps topics at positions 'i'' and 'j'' in the given assignment.
|
2019-10-17 19:50:19 +02:00
|
|
|
switch :: Int -> Int -> Assignment -> Assignment
|
|
|
|
switch i' j' xs
|
2019-10-19 15:56:44 +02:00
|
|
|
| i' == j' = xs
|
2019-10-17 19:50:19 +02:00
|
|
|
| 0 <= i' && i' < length xs && 0 <= j' && j' < length xs =
|
2023-04-26 15:47:05 +02:00
|
|
|
let i = min i' j'
|
|
|
|
j = max i' j'
|
|
|
|
ei = xs !! i
|
|
|
|
ej = xs !! j
|
|
|
|
left = take i xs
|
|
|
|
middle = take (j - i - 1) $ drop (i + 1) xs
|
|
|
|
right = drop (j + 1) xs
|
|
|
|
in left ++ [(fst ei, snd ej)] ++ middle ++ [(fst ej, snd ei)] ++ right
|
2019-10-17 19:50:19 +02:00
|
|
|
| otherwise = xs
|
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- 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
|
|
|
|
-- less topics than students).
|
|
|
|
--
|
|
|
|
-- Assumes that the priorities are well-formed.
|
2019-10-22 06:49:17 +02:00
|
|
|
valid :: Priorities -> Assignment -> Bool
|
|
|
|
valid p a =
|
2021-04-27 15:00:44 +02:00
|
|
|
-- all students must be part of the solution
|
2023-04-26 15:47:05 +02:00
|
|
|
sort (students p) == (catMaybes $ sort studentsAssigned)
|
2021-04-27 15:00:44 +02:00
|
|
|
-- each actual topic (i.e. not “no topic”) is assigned at most once
|
|
|
|
&& nubOrd (delete Nothing topicsAssigned) == delete Nothing topicsAssigned
|
2019-10-22 06:49:17 +02:00
|
|
|
where
|
|
|
|
studentsAssigned = fmap fst a
|
|
|
|
topicsAssigned = fmap snd a
|
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- prop_new_valid :: Priorities -> Property
|
|
|
|
-- prop_new_valid p = monadicIO $ do
|
|
|
|
-- I _ a <- lift $ new (I p [])
|
|
|
|
-- assert $ valid p a
|
|
|
|
|
|
|
|
-- prop_mutate_valid :: Priorities -> Property
|
|
|
|
-- prop_mutate_valid p = monadicIO $ do
|
|
|
|
-- a <- lift . new $ I p []
|
|
|
|
-- I _ a <- lift $ mutate a
|
|
|
|
-- assert $ valid p a
|
|
|
|
|
|
|
|
-- prop_crossover1_valid :: Priorities -> Property
|
|
|
|
-- prop_crossover1_valid p = monadicIO $ do
|
|
|
|
-- a1 <- lift . new $ I p []
|
|
|
|
-- a2 <- lift . new $ I p []
|
|
|
|
-- asM <- lift $ crossover1 a1 a2
|
|
|
|
-- assert
|
|
|
|
-- $ case asM of
|
|
|
|
-- Just (I _ a1', I _ a2') -> valid p a1' && valid p a2'
|
|
|
|
-- Nothing -> True
|
|
|
|
|
|
|
|
-- |
|
|
|
|
-- Generator for lists fulfilling 'unique', that is, only containing unique
|
|
|
|
-- elements.
|
2019-10-22 06:49:17 +02:00
|
|
|
noDupsList :: (Arbitrary a, Eq a, Ord a) => Gen [a]
|
|
|
|
noDupsList = nubOrd <$> arbitrary
|
2019-10-19 15:49:37 +02:00
|
|
|
|
2020-05-02 17:42:04 +02:00
|
|
|
prop_noDupsList :: Property
|
2019-10-19 15:49:37 +02:00
|
|
|
prop_noDupsList = forAll (noDupsList :: Gen [Int]) unique
|
|
|
|
|
2023-04-26 15:47:05 +02:00
|
|
|
-- |
|
|
|
|
-- Whether the given list only contains unique elements.
|
2019-10-22 06:49:17 +02:00
|
|
|
unique :: (Ord a) => [a] -> Bool
|
|
|
|
unique xs = length xs == (length . nubOrd) xs
|
|
|
|
|
2019-10-18 23:20:19 +02:00
|
|
|
return []
|
|
|
|
|
2020-05-02 17:42:04 +02:00
|
|
|
runTests :: IO Bool
|
2019-10-18 23:20:19 +02:00
|
|
|
runTests = $quickCheckAll
|