Sunday, May 15, 2022

Stable Marriage Problem - Python

 Summary

This short post covers a Python implementation of the Gale-Shapely algorithm for the Stable Marriage Problem (SMP).  Stable matching is a well-studied problem in multiple fields.  It has applications in nearly any two-sided market scenario.

Code Snippets

Preference Generator

Below is a Python function to generate a random set of preferences for two classes.  Those preferences are then subsequently used by Gale-Shapley to determine a stable matching.
  1. def generate_prefs(class1, class2):
  2. if len(class1) != len(class2):
  3. raise Exception("Invalid input: unequal list sizes")
  4.  
  5. prefs = {}
  6. for item in class1:
  7. random.shuffle(class2)
  8. prefs[item] = class2.copy()
  9. for item in class2:
  10. random.shuffle(class1)
  11. prefs[item] = class1.copy()
  12.  
  13. return dict(sorted(prefs.items()))

Gale-Shapley

  1. def gale_shapley(prefs, proposers):
  2. matches = []
  3. while len(proposers) > 0: #terminating condition - all proposers are matched
  4. proposer = proposers.pop(0) #Each round - proposer is popped from the free list
  5. proposee = prefs[proposer].pop(0) #Each round - the proposer's top preference is popped
  6. matchLen= len(matches)
  7. found = False
  8. for index in range(matchLen):
  9. match = matches[index]
  10. if proposee in match: #proposee is already matched
  11. found = True
  12. temp = match.copy()
  13. temp.remove(proposee)
  14. matchee = temp.pop()
  15. if prefs[proposee].index(proposer) < prefs[proposee].index(matchee): #proposer is a higher preference
  16. matches.remove(match) #remove old match
  17. matches.append([proposer, proposee]) #create new match with proposer
  18. proposers.append(matchee) #add the previous proposer to the free list of proposers
  19. else:
  20. proposers.append(proposer) #proposer wasn't a higher prefence, so gets put back on free list
  21. break
  22. else:
  23. continue
  24. if not found: #proposee was not previously matched so is automatically matched to proposer
  25. matches.append([proposer, proposee])
  26. else:
  27. continue
  28. return matches

Output

Below is a sample run with two three-member classes: (a1, a2, a3) and (b1, b2, b3).
Preferences
{'a1': ['b2', 'b3', 'b1'],
 'a2': ['b3', 'b2', 'b1'],
 'a3': ['b1', 'b2', 'b3'],
 'b1': ['a2', 'a1', 'a3'],
 'b2': ['a1', 'a3', 'a2'],
 'b3': ['a3', 'a1', 'a2']}

Matches
[['a3', 'b1'], ['a1', 'b2'], ['a2', 'b3']]

Source


Copyright ©1993-2024 Joey E Whelan, All rights reserved.