aboutsummaryrefslogtreecommitdiffstats
path: root/stable.py
diff options
context:
space:
mode:
Diffstat (limited to 'stable.py')
-rw-r--r--stable.py188
1 files changed, 188 insertions, 0 deletions
diff --git a/stable.py b/stable.py
new file mode 100644
index 0000000..f3bb84e
--- /dev/null
+++ b/stable.py
@@ -0,0 +1,188 @@
1#!/usr/bin/env python
2
3# The gale_shapley implementation is taken from
4# https://johnlekberg.com/blog/2020-08-22-stable-matching.html
5
6from collections import deque
7
8# Track the number of total proposals per proposer for order O questions
9number_of_proposals = dict()
10
11
12def pref_to_rank(pref):
13 return {a: {b: idx for idx, b in enumerate(a_pref)} for a, a_pref in pref.items()}
14
15
16def gale_shapley(*, A, B, A_pref, B_pref):
17 """Create a stable matching using the
18 Gale-Shapley algorithm.
19
20 A -- set[str].
21 B -- set[str].
22 A_pref -- dict[str, list[str]].
23 B_pref -- dict[str, list[str]].
24
25 Output: list of (a, b) pairs.
26 """
27 B_rank = pref_to_rank(B_pref)
28 ask_list = {a: deque(bs) for a, bs in A_pref.items()}
29 pair = {}
30
31 remaining_A = set(A)
32 while len(remaining_A) > 0:
33 a = remaining_A.pop()
34 b = ask_list[a].popleft()
35 if b not in pair:
36 pair[b] = a
37 print(f"Matching {a} with {b} (a)")
38 number_of_proposals[a] += 1
39 else:
40 a0 = pair[b]
41 b_prefer_a0 = B_rank[b][a0] < B_rank[b][a]
42 if b_prefer_a0:
43 remaining_A.add(a)
44 print(f"{a} is rejected by {b} because {a0} is more preffered (c)")
45 number_of_proposals[a] += 1
46 else:
47 remaining_A.add(a0)
48 print(f"Matching {a} with {b}, {a0} is now free (b)")
49 number_of_proposals[a] += 1
50 pair[b] = a
51
52 return [(a, b) for b, a in pair.items()]
53
54
55if __name__ == "__main__":
56
57 women = list(range(1, 27))
58 men = [chr(x + 65) for x in range(26)]
59
60 n = int(input("n: "))
61
62 men = men[:n]
63 # because algorithms are 1 indexed, compromise
64 women = ["0"] + women[:n]
65
66 men_pref = dict()
67 women_pref = dict()
68
69 # For 1 to n-1, men's preference lists look like
70 # Mk = Wk Wk+1 ... Wn-1 W1 W2 ... Wn
71
72 for (cur, m) in enumerate(men, start=1):
73
74 # last man n has the same preference list as n-1
75 if cur == n:
76 cur = cur - 1
77
78 prefs = list()
79 k = cur
80
81 # Wk .. Wn-1
82 while k < n:
83 prefs.append(women[k])
84 k += 1
85
86 # W1 .. Wn-1
87 k = 1
88 while k < cur:
89 prefs.append(women[k])
90 k += 1
91
92 # last woman n is always last at everyone's preference list
93 prefs.append(women[n])
94 men_pref[m] = prefs
95
96 # women's preference list for 1-n-2 look like
97 # Wk = Wk+1 Wk
98
99 # n-1 looks like
100 # Wn-1 = W1 Wn
101
102 # last women's preference list doesn't matter
103
104 for (cur, w) in enumerate(women, start=-1):
105
106 prefs = list()
107
108 # again, compromise between 1 indexed algorithms and nice code
109 if cur == -1:
110 continue
111
112 # last woman
113 if cur == n - 1:
114 k = 0
115 while k < n:
116 prefs.append(men[k])
117 k += 1
118 women_pref[w] = prefs
119 continue
120
121 # n-1th woman
122 if cur == n - 2:
123 prefs.append(men[0])
124 prefs.append(men[n - 1])
125
126 k = 0
127
128 while k < n:
129 if k == n - 1:
130 k += 1
131 continue
132 if k == 0:
133 k += 1
134 continue
135 prefs.append(men[k])
136 k += 1
137
138 women_pref[w] = prefs
139 continue
140
141 # rest of the women obey the general rule
142 prefs.append(men[cur + 1])
143 prefs.append(men[cur])
144
145 # we need n - k more men to fill, they don't matter
146 k = 0
147
148 while k < n:
149 if k == cur:
150 k += 1
151 continue
152 if k == cur + 1:
153 k += 1
154 continue
155 prefs.append(men[k])
156 k += 1
157
158 women_pref[w] = prefs
159
160 print("Men Preference Table")
161 for (k,v) in men_pref.items():
162 print(f'{k:2}| {" ".join([str(x) for x in v])}')
163
164 print("----")
165
166 print("Women Preference Table")
167 for (k,v) in women_pref.items():
168 print(f'{k:2}| {" ".join(v)}')
169
170 print("----")
171
172 number_of_proposals = {a: 0 for a in men}
173
174 matchings= gale_shapley(A=men, B=women, A_pref=men_pref, B_pref=women_pref)
175
176 print(f"Final pairings:")
177
178 for (m,w) in matchings:
179 print(f"{m} - {w}")
180
181 print()
182
183 total = 0
184 for (men, times) in number_of_proposals.items():
185 total += times
186 print(f"{men} proposed {times} times")
187
188 print(f"total of {total} times with n(n-1) + 1 = {n*(n-1) + 1}")