Skip to content

Commit 9de5a02

Browse files
author
TimotheeChauvin
committed
add the Gale Shapley algorithm (with a test)
1 parent a8862c3 commit 9de5a02

File tree

2 files changed

+228
-0
lines changed

2 files changed

+228
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package src.main.java.com.matchings.stableMatching;
2+
3+
public class GaleShapley {
4+
5+
/**
6+
* Return a stable matching between men and women according to their preferences,
7+
* following the Gale-Shapley algorithm.
8+
*
9+
* @param menPrefs for each man, for each preference rank, the corresponding woman
10+
* @param womenPrefs for each woman, for each preference rank, the corresponding man
11+
* @return for each man, the associated woman (1-dimensional array)
12+
*/
13+
14+
public int[] GaleShapleyStableMarriage(int[][] menPrefs, int[][] womenPrefs) {
15+
assert menPrefs.length == womenPrefs.length; // there are n individuals in each group
16+
17+
int n = menPrefs.length;
18+
if (n == 0)
19+
return new int[0]; // handle empty initial conditions right away
20+
21+
// Some implementation details: men, women and preference ranks are all labeled
22+
// from 0 to n-1.
23+
24+
// menMatching: for each man, the woman to whom he is engaged (or -1 if not engaged):
25+
int[] menMatching = new int[n];
26+
// the same for women:
27+
int[] womenMatching = new int[n];
28+
29+
// asked: for each man, highest rank asked (between 0 and n-1;
30+
// -1 if hasn't asked anyone yet)
31+
int[] asked = new int[n];
32+
33+
// Initialize all values of menMatching and womenMatching to -1,
34+
// otherwise woman 0 will be considered engaged to all men and idem for man 0.
35+
// Do the same for asked, otherwise each man will be considered as having
36+
// already asked his first choice.
37+
for (int i = 0; i < n; i++) {
38+
menMatching[i] = -1;
39+
womenMatching[i] = -1;
40+
asked[i] = -1;
41+
}
42+
43+
// to quickly retrieve the rank of men for a given woman, we create womenRanks.
44+
// For each woman, the array is:
45+
// index: man; value: rank
46+
// whereas in womenPrefs it was index: rank; value: man
47+
// Retrieving a rank will be done be simply looking up womenRanks[woman][man]
48+
int[][] womenRanks = new int[n][n];
49+
int man;
50+
for (int w = 0; w < n; w++) {
51+
for (int rank = 0; rank < n; rank++) {
52+
man = womenPrefs[w][rank];
53+
womenRanks[w][man] = rank;
54+
}
55+
}
56+
57+
int unengaged = 0; // at first all men are unengaged, we take the first one
58+
int notAsked; // first rank not asked by unengaged
59+
int prefWoman; // for the considered man, preferred woman among not asked ones
60+
int currentManPartner; // for the considered woman, current partner (-1 if none)
61+
while (unengaged != -1) { // while there is an unengaged man
62+
// unengaged is our proposing man.
63+
notAsked = asked[unengaged] + 1;
64+
prefWoman = menPrefs[unengaged][notAsked];
65+
currentManPartner = womenMatching[prefWoman];
66+
// now unengaged asks prefWoman for engagement.
67+
asked[unengaged] += 1;
68+
if (currentManPartner == -1) { // prefWoman is not engaged: the two engage
69+
menMatching[unengaged] = prefWoman;
70+
womenMatching[prefWoman] = unengaged;
71+
unengaged = getUnengaged(menMatching); // we need a new unengaged
72+
} else { // prefWoman is engaged to currentManPartner (therefore >= 0)
73+
if (womenRanks[prefWoman][unengaged] < womenRanks[prefWoman][currentManPartner]) {
74+
// prefWoman prefers unengaged: split prefWoman and currentManPartner
75+
menMatching[currentManPartner] = -1;
76+
// and engage prefWoman and unengaged
77+
menMatching[unengaged] = prefWoman;
78+
womenMatching[prefWoman] = unengaged;
79+
unengaged = getUnengaged(menMatching); // we need a new unengaged
80+
}
81+
// If prefWoman prefers currentManPartner over unengaged, nothing happens
82+
// (except that asked[unengaged] has been incremented so unengaged won't ask
83+
// prefWoman for engagement anymore).
84+
}
85+
}
86+
return menMatching;
87+
}
88+
89+
/**
90+
* Get a currently unengaged man, if there is one
91+
*
92+
* @param menMatching the current men matching array (being constructed)
93+
* @return the first man that is not engaged, or -1 if all men are engaged
94+
*/
95+
96+
public int getUnengaged(int[] menMatching) {
97+
for (int m = 0; m < menMatching.length; m++) {
98+
if (menMatching[m] == -1)
99+
return m;
100+
}
101+
return -1;
102+
}
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package src.test.java.com.matchings.stableMatching;
2+
3+
import src.main.java.com.matchings.stableMatching.GaleShapley;
4+
import org.junit.Test;
5+
6+
import static junit.framework.Assert.assertEquals;
7+
8+
import java.util.Random;
9+
import java.util.Collections; // for shuffling
10+
import java.util.ArrayList; // for shuffling
11+
import java.util.List; // for shuffling
12+
13+
public class GaleShapleyTest {
14+
15+
/**
16+
* Test a number of GaleShapley executions on pseudo-random instances of the
17+
* stable marriage problem.
18+
*/
19+
20+
@Test
21+
public void testGaleShapley() {
22+
GaleShapley galeShapley = new GaleShapley();
23+
int N = 10;
24+
int[][] menPrefs;
25+
int[][] womenPrefs;
26+
int[] GaleShapleyMenMatching; // the solution returned by GaleShapley.java
27+
// for each n from 0 to N-1, create and test an instance of the problem.
28+
for (int n = 0; n < N; n++) {
29+
System.out.println("testing n = " + n);
30+
menPrefs = new int[n][n];
31+
womenPrefs = new int[n][n];
32+
// set all other sex individuals in each individual's preference list,
33+
// then shuffle
34+
for (int i = 0; i < n; i++) {
35+
for (int j = 0; j < n; j++) {
36+
menPrefs[i][j] = j;
37+
womenPrefs[i][j] = j;
38+
}
39+
shuffleArray(menPrefs[i], i);
40+
shuffleArray(womenPrefs[i], n+i);
41+
}
42+
// Now we have pseudo-random preferences for each man and each woman.
43+
GaleShapleyMenMatching = galeShapley.GaleShapleyStableMarriage(menPrefs, womenPrefs);
44+
assertEquals("Unstable matching", true, isStable(GaleShapleyMenMatching, menPrefs, womenPrefs));
45+
}
46+
}
47+
48+
/**
49+
* Determine if the proposed menMatching is stable, i.e. if there is no
50+
* potential couple in which both members would strictly prefer being with each
51+
* other than being with their current partner.
52+
*
53+
* @param menMatching
54+
* @param menPrefs
55+
* @param womenPrefs
56+
* @return whether menMatching is stable according to menPrefs and womenPrefs
57+
*/
58+
59+
public boolean isStable(int[] menMatching, int[][] menPrefs, int[][] womenPrefs) {
60+
int n = menMatching.length;
61+
// reconstruct womenMatching (for each woman, the associated man):
62+
int[] womenMatching = new int[n];
63+
int man;
64+
int woman;
65+
for (man = 0; man < n; man++) {
66+
woman = menMatching[man];
67+
womenMatching[woman] = man;
68+
}
69+
70+
// construct menRanks and womenRanks to quickly compare preferences:
71+
int[][] menRanks = new int[n][n];
72+
int[][] womenRanks = new int[n][n];
73+
int individualAtThisRank;
74+
for (int i = 0; i < n; i++) {
75+
for (int rank = 0; rank < n; rank++) {
76+
// womenRanks
77+
individualAtThisRank = womenPrefs[i][rank];
78+
womenRanks[i][individualAtThisRank] = rank;
79+
80+
// menRanks
81+
individualAtThisRank = menPrefs[i][rank];
82+
menRanks[i][individualAtThisRank] = rank;
83+
}
84+
}
85+
86+
// Do the actual test by considering all potential n*n couples and verifying
87+
// that at least one of them is happier now than they
88+
// would be in the potential couple
89+
90+
int currentEngagedMan; // man currently engaged to considered woman
91+
int currentEngagedWoman; // woman currently engaged to considered man
92+
for (man = 0; man < n; man++) {
93+
for (woman = 0; woman < n; woman++) {
94+
currentEngagedMan = womenMatching[woman];
95+
currentEngagedWoman = menMatching[man];
96+
if (womenRanks[woman][man] < womenRanks[woman][currentEngagedMan]
97+
&& menRanks[man][woman] < menRanks[man][currentEngagedWoman]) {
98+
// man prefers woman over currentEngagedWoman, and
99+
// woman prefers man over currentEngagedMan.
100+
// The marriage therefore isn't stable.
101+
return false;
102+
}
103+
}
104+
}
105+
return true;
106+
}
107+
108+
/**
109+
* Shuffle an array using Collections.shuffle
110+
*
111+
* @param array array to be shuffled
112+
* @param seed fixed seed, for reproducibility
113+
*/
114+
115+
public void shuffleArray(int[] array, long seed) {
116+
List<Integer> list = new ArrayList<>();
117+
for (int i : array) {
118+
list.add(i);
119+
}
120+
Collections.shuffle(list, new Random(seed));
121+
for (int i = 0; i < list.size(); i++) {
122+
array[i] = list.get(i);
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)