Skip to content

Commit 77dfff6

Browse files
authored
Improved stable marriage problem Python implementation (#687)
1 parent 6517010 commit 77dfff6

File tree

2 files changed

+63
-54
lines changed

2 files changed

+63
-54
lines changed

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ This file lists everyone, who contributed to this repo and wanted to show up her
5151
- Vincent Zalzal
5252
- Jonathan D B Van Schenck
5353
- James Goytia
54+
- Amaras
Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
# Submitted by Marius Becker
2+
# Updated by Amaras
3+
24

3-
import sys
45
from random import shuffle
56
from copy import copy
6-
from string import ascii_uppercase
7+
from string import ascii_uppercase, ascii_lowercase
78

8-
def main():
9-
# Set this to however many men and women you want
10-
if len(sys.argv) > 1:
11-
num_pairs = int(sys.argv[1])
12-
else:
13-
num_pairs = 5
149

15-
# There are only 26 possible names
16-
if num_pairs > 13:
17-
print('You can\' have more than 13 pairs.')
18-
return
10+
def main():
11+
# Set this to however many men and women you want, up to 26
12+
num_pairs = 5
1913

2014
# Create all Person objects
21-
men = [ Person(name) for name in ascii_uppercase[0:num_pairs] ]
22-
women = [ Person(name) for name in ascii_uppercase[num_pairs:num_pairs*2] ]
15+
men = [Person(name) for name in ascii_uppercase[:num_pairs]]
16+
women = [Person(name) for name in ascii_lowercase[:num_pairs]]
2317

2418
# Set everyone's preferences
2519
for man in men:
@@ -31,62 +25,64 @@ def main():
3125
shuffle(woman.preference)
3226

3327
# Run the algorithm
34-
resolve(men, women)
28+
stable_marriage(men, women)
3529

3630
# Print preferences and the result
31+
print('Preferences of the men:')
3732
for man in men:
38-
print('{}: {}'.format(man.name, ', '.join([ p.name for p in man.preference ])))
33+
print(man)
34+
35+
print()
3936

37+
print('Preferences of the women:')
4038
for woman in women:
41-
print('{}: {}'.format(woman.name, ', '.join([ p.name for p in woman.preference ])))
39+
print(woman)
4240

43-
print('')
41+
print('\n')
4442

43+
print('The algorithm gave this solution:')
4544
for man in men:
46-
print('{} + {}'.format(man.name, man.partner.name))
45+
print(f'{man.name} + {man.partner.name}')
4746

48-
def resolve(men, women):
47+
48+
def stable_marriage(men, women):
4949
"""Finds pairs with stable marriages"""
50-
cont = True
51-
while cont:
50+
51+
while True:
5252
# Let every man without a partner propose to a woman
5353
for man in men:
54-
if not man.has_partner():
54+
if not man.has_partner:
5555
man.propose_to_next()
5656

5757
# Let the women pick their favorites
5858
for woman in women:
5959
woman.pick_preferred()
6060

6161
# Continue only when someone is still left without a partner
62-
cont = False
63-
for man in men:
64-
if not man.has_partner():
65-
cont = True
66-
break
62+
if all((man.has_partner for man in men)):
63+
return
64+
6765

6866
class Person:
69-
name = None
70-
preference = None
71-
pref_index = 0
72-
candidates = None
73-
partner = None
7467

7568
def __init__(self, name):
7669
self.name = name
7770
self.preference = []
7871
self.candidates = []
72+
self.pref_index = 0
73+
self._partner = None
7974

80-
def get_next_choice(self):
75+
@property
76+
def next_choice(self):
8177
"""Return the next person in the own preference list"""
82-
if self.pref_index >= len(self.preference):
78+
try:
79+
return self.preference[self.pref_index]
80+
except IndexError:
8381
return None
8482

85-
return self.preference[self.pref_index]
86-
8783
def propose_to_next(self):
8884
"""Propose to the next person in the own preference list"""
89-
person = self.get_next_choice()
85+
person = self.next_choice
9086
person.candidates.append(self)
9187
self.pref_index += 1
9288

@@ -99,33 +95,45 @@ def pick_preferred(self):
9995
if person == self.partner:
10096
break
10197
elif person in self.candidates:
102-
self.set_partner(person)
98+
self.partner = person
10399
break
104100

105-
# Rejected candidates don't get a second chance. :(
101+
# Rejected candidates don't get a second chance
106102
self.candidates.clear()
107103

108-
def get_partner(self):
109-
"""Return the current partner"""
110-
return self.partner
104+
@property
105+
def partner(self):
106+
return self._partner
107+
108+
# The call self.partner = person sets self._partner as person
109+
# However, since engagement is symmetrical, self._partner._partner
110+
# (which is then person._partner) also needs to be set to self
111+
@partner.setter
112+
def partner(self, person):
113+
"""Set a person as the new partner and sets the partner of that
114+
person as well"""
111115

112-
def set_partner(self, person):
113-
"""Set a person as the new partner and run set_partner() on that person
114-
as well"""
115116
# Do nothing if nothing would change
116-
if person != self.partner:
117+
if person != self._partner:
117118
# Remove self from current partner
118-
if self.partner is not None:
119-
self.partner.partner = None
119+
if self._partner is not None:
120+
self._partner._partner = None
120121

121122
# Set own and the other person's partner
122-
self.partner = person
123-
if self.partner is not None:
124-
self.partner.partner = self
123+
self._partner = person
124+
if self._partner is not None:
125+
self._partner._partner = self
125126

127+
# This allows use of self.has_partner instead of self.has_partner()
128+
@property
126129
def has_partner(self):
127-
"""Determine whether this person currently has a partner or not"""
128-
return self.partner != None
130+
"""Determine whether this person currently has a partner or not."""
131+
return self.partner is not None
132+
133+
# This allows the preferences to be printed more elegantly
134+
def __str__(self):
135+
return f'{self.name}: {", ".join(p.name for p in self.preference)}'
136+
129137

130138
if __name__ == '__main__':
131139
main()

0 commit comments

Comments
 (0)