summaryrefslogtreecommitdiff
path: root/ss2017/stable_marriage_problem/ausarbeitung.lhs
blob: ca032809d86625a408f9df7c2a9885f57a1f403a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
Preliminaries
=============

  - Efficient finite maps

> import Data.Map (Map, (!))
> import qualified Data.Map as Map

  - Efficient finite sets

> import Data.Set (Set)
> import qualified Data.Set as Set

  - Some convenience functions for manipulating lists and endomorphisms

> import Control.Monad (guard, when)
> import Data.Monoid (Endo(..))

  - An arbitrary representation of a person

> type Person = Integer

Utilities
---------

> (&!) :: a -> [a -> a] -> a
> -- ^ Reverse application of a list of endomorphisms
> x &! fs = foldMap Endo fs `appEndo` x
> 
> infix 3 ==>
> -- | Logical implication
> (==>) :: Bool -> Bool -> Bool
> False ==> _     = True
> True  ==> True  = True
> True  ==> False = False

<!--

> pPrint :: PreferenceTable -> IO ()
> pPrint = putStr . unlines . map (\(k, prefs) -> show k ++ ": " ++ unwords (map show prefs)) . Map.toList
>
> stepPhase1 :: PreferenceTable -> IO (Maybe PreferenceTable)
> stepPhase1 = phase1Iter Set.empty
>   where
>     phase1Iter :: Set Person -> PreferenceTable -> IO (Maybe PreferenceTable)
>     -- ^ Call `phase1'` until no person is free
>     phase1Iter engaged table = do
>         let r = phase1' engaged table
>         case r of
>             Just (engaged', table') -> do
>                 when (table' /= table) $ do
>                     pPrint table'
>                     () <$ getLine
>                 if engaged' == Map.keysSet table'
>                     then return (Just table')
>                     else phase1Iter engaged' table'
>             Nothing -> return Nothing


-->

Definitions
===========

\begin{def*}
A \emph{preference table} is a finite set of persons, each associated with a list of other persons, ordered by preference.
\end{def*}

> type PreferenceTable = Map Person [Person]

\begin{def*}
We call a pair ${a, b}$ \emph{embedded} in a preference table if $a$ occurs in $b$s preference list and vice versa.
\end{def*}

Deleting a pair from a table means removing each from the others preference list:

> delPair :: (Person, Person) -> (PreferenceTable -> PreferenceTable)
> delPair (p1, p2) = Map.mapWithKey delPair'
>   where
>     delPair' k v = do
>         z <- v
>         guard $ k == p1 ==> z /= p2
>         guard $ k == p2 ==> z /= p1
>         return z

\begin{def*}
An ordered pair $(a, b)$ is said to be \emph{semiengaged} if $a$ occurs last in $b$s preference table and $b$ occurs first in $a$s.

This means that, while $b$ is $a$s best choice of partner, $a$ has no worse choice in partner than $a$
\end{def*}

> semiEngaged :: (Person, Person) -> PreferenceTable -> Bool
> -- ^ `semiEngaged (x, y)`; is `x` last in `y`s preference list?
> semiEngaged (x, y) table = x == (last $ table ! y)

Phase 1
=======

> phase1' :: Set Person -> PreferenceTable -> Maybe (Set Person, PreferenceTable)
> phase1' engaged table
>   | any null (Map.elems table)
>   = Nothing -- If a preference list became empty: fail
>   | Set.null free
>   = Just (engaged, table) -- If nobody is free, we have nobody left to engage
>   | otherwise
>   = let x = Set.findMin free -- Arbitrary choice of free person
>         y = head $ table ! x -- Best choice of engagement for x
>         adjTable = [ delPair (x', y) | x' <- dropWhile (/= x) (table ! y), x' /= x ]
>         -- ^ For all worse (than x) choices x' for y; drop them from y's preference list
>         adjEngaged = [ Set.insert x -- x is now engaged
>                      ] ++ [ Set.delete z | z <- Map.keys table, semiEngaged (z, y) table, z /= x ] -- Nobody else is engaged to y
>      in Just (engaged &! adjEngaged, table &! adjTable)
>   where
>     free = Map.keysSet table `Set.difference` engaged

The algorithm is iterated until all participants are engaged

> phase1 :: PreferenceTable -> Maybe PreferenceTable
> phase1 = phase1Iter Set.empty
>   where
>     phase1Iter :: Set Person -> PreferenceTable -> Maybe PreferenceTable
>     -- ^ Call `phase1'` until no person is free
>     phase1Iter engaged table = do
>         (engaged', table') <- phase1' engaged table
>         if engaged' == Map.keysSet table'
>             then return table'
>             else phase1Iter engaged' table'