Skip to content

Instantly share code, notes, and snippets.

@joyrexus
Last active February 12, 2024 19:59
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 15 You must be signed in to fork a gist
  • Save joyrexus/9967709 to your computer and use it in GitHub Desktop.
Save joyrexus/9967709 to your computer and use it in GitHub Desktop.
The Stable Marriage Problem

My implementation of the Gale/Shapley algorithm in Python. This algorithm is designed to address the Stable Marriage Problem.

Compare this recursive variant with the implementations on Rosetta Code.

Problem description

Given an equal number of men and women to be paired for marriage, each man ranks all the women in order of his preference and each women ranks all the men in order of her preference.

A stable set of engagements for marriage is one where no man prefers a women over the one he is engaged to, where that other woman also prefers that man over the one she is engaged to. I.e. with consulting marriages, there would be no reason for the engagements between the people to change.

Gale and Shapley proved that there is a stable set of engagements for any set of preferences and the first link above gives their algorithm for finding a set of stable engagements.

Task Specifics

We're provided with a list of men (M) and women (W), indicating each mans (and womans) spousal preferences ranked from highest to lowest.

The task is to implement the Gale-Shapley algorithm for finding a stable set of engagements.

We want to use this algorithm to produce a matching, which we can then test for stability by the criteria indicated above.

We're also asked to perturb the resulting matching (swapping the spouses of two couples) and re-test for stability. The perturbed matching should be found to be unstable.

See Also

from collections import defaultdict
class Matcher:
def __init__(self, men, women):
'''
Constructs a Matcher instance.
Takes a dict of men's spousal preferences, `men`,
and a dict of women's spousal preferences, `women`.
'''
self.M = men
self.W = women
self.wives = {}
self.pairs = []
# we index spousal preferences at initialization
# to avoid expensive lookups when matching
self.mrank = defaultdict(dict) # `mrank[m][w]` is m's ranking of w
self.wrank = defaultdict(dict) # `wrank[w][m]` is w's ranking of m
for m, prefs in men.items():
for i, w in enumerate(prefs):
self.mrank[m][w] = i
for w, prefs in women.items():
for i, m in enumerate(prefs):
self.wrank[w][m] = i
def __call__(self):
return self.match()
def prefers(self, w, m, h):
'''Test whether w prefers m over h.'''
return self.wrank[w][m] < self.wrank[w][h]
def after(self, m, w):
'''Return the woman favored by m after w.'''
i = self.mrank[m][w] + 1 # index of woman following w in list of prefs
return self.M[m][i]
def match(self, men=None, next=None, wives=None):
'''
Try to match all men with their next preferred spouse.
'''
if men is None:
men = self.M.keys() # get the complete list of men
if next is None:
# if not defined, map each man to their first preference
next = dict((m, rank[0]) for m, rank in self.M.items())
if wives is None:
wives = {} # mapping from women to current spouse
if not len(men):
self.pairs = [(h, w) for w, h in wives.items()]
self.wives = wives
return wives
m, men = men[0], men[1:]
w = next[m] # next woman for m to propose to
next[m] = self.after(m, w) # woman after w in m's list of prefs
if w in wives:
h = wives[w] # current husband
if self.prefers(w, m, h):
men.append(h) # husband becomes available again
wives[w] = m # w becomes wife of m
else:
men.append(m) # m remains unmarried
else:
wives[w] = m # w becomes wife of m
return self.match(men, next, wives)
def is_stable(self, wives=None, verbose=False):
if wives is None:
wives = self.wives
for w, m in wives.items():
i = self.M[m].index(w)
preferred = self.M[m][:i]
for p in preferred:
h = wives[p]
if self.W[p].index(m) < self.W[p].index(h):
msg = "{}'s marriage to {} is unstable: " + \
"{} prefers {} over {} and {} prefers " + \
"{} over her current husband {}"
if verbose:
print msg.format(m, w, m, p, w, p, m, h)
return False
return True
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
from match import Matcher
# the men and their list of ordered spousal preferences
M = dict((m, prefs.split(', ')) for [m, prefs] in (line.rstrip().split(': ')
for line in open('men.txt')))
# the women and their list of ordered spousal preferences
W = dict((m, prefs.split(', ')) for [m, prefs] in (line.rstrip().split(': ')
for line in open('women.txt')))
# initialize Matcher with preference lists for both men and women
match = Matcher(M, W)
# check if the mapping of wives to husbands is stable
def is_stable(wives, verbose=False):
for w, m in wives.items():
i = M[m].index(w)
preferred = M[m][:i]
for p in preferred:
h = wives[p]
if W[p].index(m) < W[p].index(h):
msg = "{}'s marriage to {} is unstable: " + \
"{} prefers {} over {} and {} prefers " + \
"{} over her current husband {}"
if verbose:
print msg.format(m, w, m, p, w, p, m, h)
return False
return True
# match men and women; returns a mapping of wives to husbands
wives = match()
assert is_stable(wives) # should be a stable matching
assert match.is_stable() # should be a stable matching
# swap the husbands of two wives, which should make the matching unstable
wives['fay'], wives['gay'] = wives['gay'], wives['fay']
assert is_stable(wives) is False # should not be a stable matching
# with the perturbed matching we find that gav's marriage to fay is unstable:
#
# * gav prefers gay over fay
# * gay prefers gav over her current husband dan
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
@shyankirat
Copy link

m, men = men[0], men[1:]
TypeError: 'dict_keys' object does not support indexing

I am getting this error while executing in python 3. Is there any solution for this as I am new in python.

@jpivarski
Copy link

Change m, men = men[0], men[1:] to m, men = list(men)[0], list(men)[1:] to get the same behavior in Python 3 as in Python 2.

@dipunj
Copy link

dipunj commented Aug 12, 2018

@joyrexus In your implementation, do men send proposals to women or is it the other way round?

@shadowfox5115
Copy link

what is the output of this code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment