Skip to content

Commit 015a603

Browse files
committed
Implement Stable_marriage_problem Gale-Shapley algo
0 parents  commit 015a603

File tree

1 file changed

+121
-0
lines changed

1 file changed

+121
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import sequtils, std/enumerate, random, strutils
2+
const PAIRS = 10
3+
const m_names = ["abe", "bob", "col", "dan", "ed", "fred", "gav", "hal", "ian", "jon"]
4+
const f_names = ["abi", "bea", "cath", "dee", "eve", "fay", "gay", "hope",
5+
"ivy", "jan"]
6+
const m_prefs = [
7+
["abi", "eve", "cath", "ivy", "jan", "dee", "fay", "bea", "hope", "gay"],
8+
["cath", "hope", "abi", "dee", "eve", "fay", "bea", "jan", "ivy", "gay"],
9+
["hope", "eve", "abi", "dee", "bea", "fay", "ivy", "gay", "cath", "jan"],
10+
["ivy", "fay", "dee", "gay", "hope", "eve", "jan", "bea", "cath", "abi"],
11+
["jan", "dee", "bea", "cath", "fay", "eve", "abi", "ivy", "hope", "gay"],
12+
["bea", "abi", "dee", "gay", "eve", "ivy", "cath", "jan", "hope", "fay"],
13+
["gay", "eve", "ivy", "bea", "cath", "abi", "dee", "hope", "jan", "fay"],
14+
["abi", "eve", "hope", "fay", "ivy", "cath", "jan", "bea", "gay", "dee"],
15+
["hope", "cath", "dee", "gay", "bea", "abi", "fay", "ivy", "jan", "eve"],
16+
["abi", "fay", "jan", "gay", "eve", "bea", "dee", "cath", "ivy", "hope"]
17+
]
18+
const f_prefs = [
19+
["bob", "fred", "jon", "gav", "ian", "abe", "dan", "ed", "col", "hal"],
20+
["bob", "abe", "col", "fred", "gav", "dan", "ian", "ed", "jon", "hal"],
21+
["fred", "bob", "ed", "gav", "hal", "col", "ian", "abe", "dan", "jon"],
22+
["fred", "jon", "col", "abe", "ian", "hal", "gav", "dan", "bob", "ed"],
23+
["jon", "hal", "fred", "dan", "abe", "gav", "col", "ed", "ian", "bob"],
24+
["bob", "abe", "ed", "ian", "jon", "dan", "fred", "gav", "col", "hal"],
25+
["jon", "gav", "hal", "fred", "bob", "abe", "col", "ed", "dan", "ian"],
26+
["gav", "jon", "bob", "abe", "ian", "dan", "hal", "ed", "col", "fred"],
27+
["ian", "col", "hal", "gav", "fred", "bob", "abe", "ed", "jon", "dan"],
28+
["ed", "hal", "gav", "abe", "bob", "jon", "col", "ian", "fred", "dan"]
29+
]
30+
31+
# recipient's preferences hold the preference score for each contender's id
32+
func get_rec_prefs[N: static int](prefs: array[N, array[N, string]],
33+
names: openArray[string]): seq[seq[int]] {.compileTime.} =
34+
for pref_seq in prefs:
35+
var p = newSeq[int](PAIRS)
36+
for contender in 0..<PAIRS:
37+
p[contender] = pref_seq.find(m_names[contender])
38+
result.add(p)
39+
40+
# contender's preferences hold the recipient ids in descending order of preference
41+
func get_cont_prefs(prefs: array[PAIRS, array[PAIRS, string]], names: openArray[
42+
string]): seq[seq[int]] {.compileTime.} =
43+
for pref_seq in prefs:
44+
var p: seq[int]
45+
for pref in pref_seq:
46+
p.add(names.find(pref))
47+
result.add(p)
48+
49+
const RECIPIENT_PREFS = get_rec_prefs(f_prefs, m_names)
50+
const CONTENDER_PREFS = get_cont_prefs(m_prefs, f_names)
51+
52+
proc print_couples(cont_pairs: seq[int]) =
53+
for c, r in enumerate(cont_pairs):
54+
echo m_names[c] & " 💑" & f_names[cont_pairs[c]]
55+
56+
func pair(): (seq[int], seq[int]) =
57+
# double booking to avoid inverse lookup using find
58+
var rec_pairs = newSeqWith(10, -1)
59+
var cont_pairs = newSeqWith(10, -1)
60+
proc engage(c, r: int) =
61+
#echo f_names[r] & " accepted " & m_names[c]
62+
cont_pairs[c] = r
63+
rec_pairs[r] = c
64+
var cont_queue = newSeqWith(10, 0)
65+
while cont_pairs.contains(-1):
66+
for c in 0..<PAIRS:
67+
if cont_pairs[c] == -1:
68+
let r = CONTENDER_PREFS[c][cont_queue[c]] #proposing to first in queue
69+
cont_queue[c]+=1 #increment contender's queue for future iterations
70+
let cur_pair = rec_pairs[r] # current pair's index or -1 = vacant
71+
if cur_pair == -1:
72+
engage(c, r)
73+
# contender is more preferable than current
74+
elif RECIPIENT_PREFS[r][c] < RECIPIENT_PREFS[r][cur_pair]:
75+
cont_pairs[cur_pair] = -1 # vacate current pair
76+
#echo m_names[cur_pair] & " was dumped by " & f_names[r]
77+
engage(c, r)
78+
result = (cont_pairs, rec_pairs)
79+
80+
proc rand_pair(max: int): (int, int) =
81+
let a = rand(max)
82+
var b = rand(max-1)
83+
if b == a:
84+
b = max
85+
result = (a,b)
86+
87+
proc perturb_pairs(cont_pairs, rec_pairs: var seq[int]) =
88+
randomize()
89+
let (a,b) = rand_pair(PAIRS-1)
90+
echo "Swapping " & m_names[a] & " & " & m_names[b] & " partners"
91+
swap(cont_pairs[a], cont_pairs[b])
92+
swap(rec_pairs[cont_pairs[a]], rec_pairs[cont_pairs[b]])
93+
94+
proc check_stability(cont_pairs, rec_pairs: seq[int]): bool =
95+
for c in 0..<PAIRS: # each contender
96+
let cur_p_score = CONTENDER_PREFS[c].find(cont_pairs[c]) # pref. score for current pair
97+
for preferred_id in 0..<cur_p_score: # try every recipient with higher score
98+
let check_r = CONTENDER_PREFS[c][preferred_id]
99+
let cur_r_p = rec_pairs[check_r] # current pair of checked recipient
100+
# if score of the cur_r_p is worse (>) than score of checked contender
101+
if RECIPIENT_PREFS[check_r][cur_r_p] > RECIPIENT_PREFS[check_r][c]:
102+
echo m_names[c] & " prefers " & f_names[check_r] & " over " & f_names[cont_pairs[c]]
103+
echo f_names[check_r] & " prefers " & m_names[c] & " over " & m_names[cur_r_p]
104+
return false # unstable
105+
result = true
106+
107+
when isMainModule:
108+
var (cont_pairs, rec_pairs) = pair()
109+
print_couples(cont_pairs)
110+
echo "Current pair analysis:"
111+
echo if check_stability(cont_pairs, rec_pairs):
112+
"✓ Stable"
113+
else:
114+
"✗ Unstable"
115+
perturb_pairs(cont_pairs, rec_pairs)
116+
print_couples(cont_pairs)
117+
echo "Current pair analysis:"
118+
echo if check_stability(cont_pairs, rec_pairs):
119+
"✓ Stable"
120+
else:
121+
"✗ Unstable"

0 commit comments

Comments
 (0)