Stable marriage problem: Difference between revisions
Content added Content deleted
(→{{header|UNIX Shell}}: Add implementation.) |
(→{{header|Haskell}}: Changed to more idiomatic and clear solution) |
||
Line 2,528: | Line 2,528: | ||
=={{header|Haskell}}== |
=={{header|Haskell}}== |
||
<lang haskell>import Data.List |
|||
import Control.Monad |
|||
import Control.Arrow |
|||
import Data.Maybe |
|||
=== The solution === |
|||
mp = map ((head &&& tail). splitNames) |
|||
["abe: abi, eve, cath, ivy, jan, dee, fay, bea, hope, gay", |
|||
"bob: cath, hope, abi, dee, eve, fay, bea, jan, ivy, gay", |
|||
"col: hope, eve, abi, dee, bea, fay, ivy, gay, cath, jan", |
|||
"dan: ivy, fay, dee, gay, hope, eve, jan, bea, cath, abi", |
|||
"ed: jan, dee, bea, cath, fay, eve, abi, ivy, hope, gay", |
|||
"fred: bea, abi, dee, gay, eve, ivy, cath, jan, hope, fay", |
|||
"gav: gay, eve, ivy, bea, cath, abi, dee, hope, jan, fay", |
|||
"hal: abi, eve, hope, fay, ivy, cath, jan, bea, gay, dee", |
|||
"ian: hope, cath, dee, gay, bea, abi, fay, ivy, jan, eve", |
|||
"jon: abi, fay, jan, gay, eve, bea, dee, cath, ivy, hope"] |
|||
fp = map ((head &&& tail). splitNames) |
|||
["abi: bob, fred, jon, gav, ian, abe, dan, ed, col, hal", |
|||
"bea: bob, abe, col, fred, gav, dan, ian, ed, jon, hal", |
|||
"cath: fred, bob, ed, gav, hal, col, ian, abe, dan, jon", |
|||
"dee: fred, jon, col, abe, ian, hal, gav, dan, bob, ed", |
|||
"eve: jon, hal, fred, dan, abe, gav, col, ed, ian, bob", |
|||
"fay: bob, abe, ed, ian, jon, dan, fred, gav, col, hal", |
|||
"gay: jon, gav, hal, fred, bob, abe, col, ed, dan, ian", |
|||
"hope: gav, jon, bob, abe, ian, dan, hal, ed, col, fred", |
|||
"ivy: ian, col, hal, gav, fred, bob, abe, ed, jon, dan", |
|||
"jan: ed, hal, gav, abe, bob, jon, col, ian, fred, dan"] |
|||
The Gale/Shapley algorithm is formulated via iterative changing of the state. In Haskell it is possible to implement this approach by pure function iterations. |
|||
splitNames = map (takeWhile(`notElem`",:")). words |
|||
The state here consists of the list of free guys and associative preferences lists for guys and girls correspondingly. In order to simplify the access to elements of the state we use lenses. |
|||
pref x y xs = fromJust (elemIndex x xs) < fromJust (elemIndex y xs) |
|||
<lang haskell>{-# LANGUAGE TemplateHaskell #-} |
|||
task ms fs = do |
|||
import Lens.Micro |
|||
let |
|||
import Lens.Micro.TH |
|||
jos = fst $ unzip ms |
|||
import Data.List (union, delete) |
|||
runGS es js ms = do |
|||
let (m:js') = js |
|||
(v:vm') = case lookup m ms of |
|||
Just xs -> xs |
|||
_ -> [] |
|||
vv = fromJust $ lookup v fs |
|||
m2 = case lookup v es of |
|||
Just e -> e |
|||
_ -> "" |
|||
ms1 = insert (m,vm') $ delete (m,v:vm') ms |
|||
data State = State { _freeGuys :: [String] |
|||
if null js then do |
|||
, _guys :: [(String,[String])] |
|||
putStrLn "" |
|||
, _girls :: [(String,[String])]} |
|||
putStrLn "=== Couples ===" |
|||
return es |
|||
else if null m2 then |
|||
do putStrLn $ v ++ " with " ++ m |
|||
runGS ( insert (v,m) es ) js' ms1 |
|||
else if pref m m2 vv then |
|||
do putStrLn $ v ++ " dumped " ++ m2 ++ " for " ++ m |
|||
runGS ( insert (v,m) $ delete (v,m2) es ) (if not $ null vm' then js'++[m2] else js') ms1 |
|||
makeLenses ''State</lang> |
|||
else runGS es (if not $ null js' then js'++[m] else js') ms1 |
|||
Lenses allow us to get access to each person in the state, and even to the associated preference list: |
|||
cs <- runGS [] jos ms |
|||
mapM_ (\(f,m) -> putStrLn $ f ++ " with " ++ m ) cs |
|||
putStrLn "" |
|||
checkStab cs |
|||
putStrLn "" |
|||
putStrLn "Introducing error: " |
|||
let [r1@(a,b), r2@(p,q)] = take 2 cs |
|||
r3 = (a,q) |
|||
r4 = (p,b) |
|||
errcs = insert r4. insert r3. delete r2 $ delete r1 cs |
|||
putStrLn $ "\tSwapping partners of " ++ a ++ " and " ++ p |
|||
putStrLn $ (\((a,b),(p,q)) -> "\t" ++ a ++ " is now with " ++ b ++ " and " ++ p ++ " with " ++ q) (r3,r4) |
|||
putStrLn "" |
|||
checkStab errcs |
|||
<lang Haskell>name n = lens get set |
|||
checkStab es = do |
|||
where get = head . dropWhile ((/= n).fst) |
|||
let |
|||
set assoc (_,v) = let (prev, _:post) = break ((== n).fst) assoc |
|||
fmt (a,b,c,d) = a ++ " and " ++ b ++ " like each other better than their current partners " ++ c ++ " and " ++ d |
|||
in prev ++ (n, v):post |
|||
slb = map (\(f,m)-> (f,m, map (id &&& fromJust. flip lookup ies). fst.break(==m). fromJust $ lookup f fp) ) es |
|||
hlb = map (\(f,m)-> (m,f, map (id &&& fromJust. flip lookup es ). fst.break(==f). fromJust $ lookup m mp) ) es |
|||
tslb = concatMap (filter snd. (\(f,m,ls) -> |
|||
map (\(m2,f2) -> |
|||
((f,m2,f2,m), pref f f2 $ fromJust $ lookup m2 mp)) ls)) slb |
|||
thlb = concatMap (filter snd. (\(m,f,ls) -> |
|||
map (\(f2,m2) -> |
|||
((m,f2,m2,f), pref m m2 $ fromJust $ lookup f2 fp)) ls)) hlb |
|||
res = tslb ++ thlb |
|||
if not $ null res then do |
|||
putStrLn "Marriages are unstable, e.g.:" |
|||
putStrLn.fmt.fst $ head res |
|||
fianceesOf n = guys.name n._2 |
|||
else putStrLn "Marriages are stable"</lang> |
|||
fiancesOf n = girls.name n._2 |
|||
{{out}} |
|||
<pre>*Main> task mp fp |
|||
theBestGirlFor name = fianceesOf name._head |
|||
abi with abe |
|||
theBestGuyFor name = fiancesOf name._head</lang> |
|||
cath with bob |
|||
hope with col |
|||
Here we see that the best choice for guys and for girls is expected to appear on the top of their preference lists. Note that in following we use lens operators: |
|||
ivy with dan |
|||
jan with ed |
|||
^. -- access to a field |
|||
bea with fred |
|||
%~ -- modification of a field |
|||
gay with gav |
|||
.~ -- setting a field the value |
|||
hope dumped col for ian |
|||
abi dumped abe for jon |
|||
Further we use a trick: guys list girls in a descending order of preference (the most liked is the first), while girls expect guys in opposite order -- the most liked is the last. |
|||
eve with hal |
|||
dee with col |
|||
With these tools and notes we are ready to implement the Gale/Shapley algorithm and the stability test as they are given in a textbook: |
|||
ivy dumped dan for abe |
|||
fay with dan |
|||
<lang Haskell>stableMatching = getPairs . iterateUntil (null._freeGuys) step |
|||
where |
|||
iterateUntil p f = head . dropWhile (not . p) . iterate f |
|||
getPairs s = map (_2 %~ head) $ s^.guys |
|||
step s = foldl propose s (s^.freeGuys) |
|||
where |
|||
propose s guy = |
|||
let girl = s^.theBestGirlFor guy |
|||
bestGuy : otherGuys = s^.fiancesOf girl |
|||
modify |
|||
| guy == bestGuy = freeGuys %~ delete guy |
|||
| guy `elem` otherGuys = (fiancesOf girl %~ dropWhile (/= guy)) . |
|||
(freeGuys %~ guy `replaceBy` bestGuy) |
|||
| otherwise = fianceesOf guy %~ tail |
|||
in modify s |
|||
replaceBy x y [] = [] |
|||
replaceBy x y (h:t) | h == x = y:t |
|||
| otherwise = h:replaceBy x y t |
|||
unstablePairs s pairs = |
|||
[ ((m1, w1), (m2,w2)) | (m1, w1) <- pairs |
|||
, (m2,w2) <- pairs |
|||
, m1 /= m2 |
|||
, let fm = s^.fianceesOf m1 |
|||
, elemIndex w2 fm < elemIndex w1 fm |
|||
, let fw = s^.fiancesOf w2 |
|||
, elemIndex m2 fw < elemIndex m1 fw ]</lang> |
|||
=== The task === |
|||
Here are the given preferences: |
|||
<lang Haskell>guys0 = |
|||
[("abe", ["abi", "eve", "cath", "ivy", "jan", "dee", "fay", "bea", "hope", "gay"]), |
|||
("bob", ["cath", "hope", "abi", "dee", "eve", "fay", "bea", "jan", "ivy", "gay"]), |
|||
("col", ["hope", "eve", "abi", "dee", "bea", "fay", "ivy", "gay", "cath", "jan"]), |
|||
("dan", ["ivy", "fay", "dee", "gay", "hope", "eve", "jan", "bea", "cath", "abi"]), |
|||
("ed", ["jan", "dee", "bea", "cath", "fay", "eve", "abi", "ivy", "hope", "gay"]), |
|||
("fred",["bea", "abi", "dee", "gay", "eve", "ivy", "cath", "jan", "hope", "fay"]), |
|||
("gav", ["gay", "eve", "ivy", "bea", "cath", "abi", "dee", "hope", "jan", "fay"]), |
|||
("hal", ["abi", "eve", "hope", "fay", "ivy", "cath", "jan", "bea", "gay", "dee"]), |
|||
("ian", ["hope", "cath", "dee", "gay", "bea", "abi", "fay", "ivy", "jan", "eve"]), |
|||
("jon", ["abi", "fay", "jan", "gay", "eve", "bea", "dee", "cath", "ivy", "hope"])] |
|||
girls0 = |
|||
[("abi", ["bob", "fred", "jon", "gav", "ian", "abe", "dan", "ed", "col", "hal"]), |
|||
("bea", ["bob", "abe", "col", "fred", "gav", "dan", "ian", "ed", "jon", "hal"]), |
|||
("cath", ["fred", "bob", "ed", "gav", "hal", "col", "ian", "abe", "dan", "jon"]), |
|||
("dee", ["fred", "jon", "col", "abe", "ian", "hal", "gav", "dan", "bob", "ed"]), |
|||
("eve", ["jon", "hal", "fred", "dan", "abe", "gav", "col", "ed", "ian", "bob"]), |
|||
("fay", ["bob", "abe", "ed", "ian", "jon", "dan", "fred", "gav", "col", "hal"]), |
|||
("gay", ["jon", "gav", "hal", "fred", "bob", "abe", "col", "ed", "dan", "ian"]), |
|||
("hope", ["gav", "jon", "bob", "abe", "ian", "dan", "hal", "ed", "col", "fred"]), |
|||
("ivy", ["ian", "col", "hal", "gav", "fred", "bob", "abe", "ed", "jon", "dan"]), |
|||
("jan", ["ed", "hal", "gav", "abe", "bob", "jon", "col", "ian", "fred", "dan"])]</lang> |
|||
The initial state: |
|||
<lang Haskell>s0 = State (fst <$> guys0) guys0 ((_2 %~ reverse) <$> girls0)</lang> |
|||
=== Couples === |
|||
abi with jon |
|||
bea with fred |
|||
cath with bob |
|||
dee with col |
|||
eve with hal |
|||
fay with dan |
|||
gay with gav |
|||
hope with ian |
|||
ivy with abe |
|||
jan with ed |
|||
And the solution: |
|||
Marriages are stable |
|||
<pre>λ> let pairs = stableMatching s0 |
|||
λ> mapM_ print pairs |
|||
("abe","ivy") |
|||
("bob","cath") |
|||
("col","dee") |
|||
("dan","fay") |
|||
("ed","jan") |
|||
("fred","bea") |
|||
("gav","gay") |
|||
("hal","eve") |
|||
("ian","hope") |
|||
("jon","abi") |
|||
λ> unstablePairs s0 pairs |
|||
[]</pre> |
|||
Lets' make some perturbations: swap fiancees of abe and bob: |
|||
Introducing error: |
|||
Swapping partners of abi and bea |
|||
abi is now with fred and bea with jon |
|||
<pre>λ> let fiance n = name n._2 |
|||
Marriages are unstable, e.g.: |
|||
λ> let pairs' = pairs & (fiance "abe" .~ "cath") & (fiance "bob" .~ "ivy") |
|||
bea and fred like each other better than their current partners abi and jon</pre> |
|||
λ> mapM_ print $ unstablePairs s0 pairs' |
|||
(("bob","ivy"),("abe","cath")) |
|||
(("bob","ivy"),("dan","fay")) |
|||
(("bob","ivy"),("fred","bea")) |
|||
(("bob","ivy"),("ian","hope")) |
|||
(("bob","ivy"),("jon","abi"))</pre> |
|||
=={{header|Icon}} and {{header|Unicon}}== |
=={{header|Icon}} and {{header|Unicon}}== |