Merge pull request #691 from TimotheeChauvin/Development
add the Gale Shapley algorithm and its test
This commit is contained in:
commit
e631e3214b
103
src/main/java/com/matchings/stableMatching/GaleShapley.java
Normal file
103
src/main/java/com/matchings/stableMatching/GaleShapley.java
Normal file
@ -0,0 +1,103 @@
|
||||
package src.main.java.com.matchings.stableMatching;
|
||||
|
||||
public class GaleShapley {
|
||||
|
||||
/**
|
||||
* Return a stable matching between men and women according to their preferences,
|
||||
* following the Gale-Shapley algorithm.
|
||||
*
|
||||
* @param menPrefs for each man, for each preference rank, the corresponding woman
|
||||
* @param womenPrefs for each woman, for each preference rank, the corresponding man
|
||||
* @return for each man, the associated woman (1-dimensional array)
|
||||
*/
|
||||
|
||||
public int[] GaleShapleyStableMarriage(int[][] menPrefs, int[][] womenPrefs) {
|
||||
assert menPrefs.length == womenPrefs.length; // there are n individuals in each group
|
||||
|
||||
int n = menPrefs.length;
|
||||
if (n == 0)
|
||||
return new int[0]; // handle empty initial conditions right away
|
||||
|
||||
// Some implementation details: men, women and preference ranks are all labeled
|
||||
// from 0 to n-1.
|
||||
|
||||
// menMatching: for each man, the woman to whom he is engaged (or -1 if not engaged):
|
||||
int[] menMatching = new int[n];
|
||||
// the same for women:
|
||||
int[] womenMatching = new int[n];
|
||||
|
||||
// asked: for each man, highest rank asked (between 0 and n-1;
|
||||
// -1 if hasn't asked anyone yet)
|
||||
int[] asked = new int[n];
|
||||
|
||||
// Initialize all values of menMatching and womenMatching to -1,
|
||||
// otherwise woman 0 will be considered engaged to all men and idem for man 0.
|
||||
// Do the same for asked, otherwise each man will be considered as having
|
||||
// already asked his first choice.
|
||||
for (int i = 0; i < n; i++) {
|
||||
menMatching[i] = -1;
|
||||
womenMatching[i] = -1;
|
||||
asked[i] = -1;
|
||||
}
|
||||
|
||||
// to quickly retrieve the rank of men for a given woman, we create womenRanks.
|
||||
// For each woman, the array is:
|
||||
// index: man; value: rank
|
||||
// whereas in womenPrefs it was index: rank; value: man
|
||||
// Retrieving a rank will be done be simply looking up womenRanks[woman][man]
|
||||
int[][] womenRanks = new int[n][n];
|
||||
int man;
|
||||
for (int w = 0; w < n; w++) {
|
||||
for (int rank = 0; rank < n; rank++) {
|
||||
man = womenPrefs[w][rank];
|
||||
womenRanks[w][man] = rank;
|
||||
}
|
||||
}
|
||||
|
||||
int unengaged = 0; // at first all men are unengaged, we take the first one
|
||||
int notAsked; // first rank not asked by unengaged
|
||||
int prefWoman; // for the considered man, preferred woman among not asked ones
|
||||
int currentManPartner; // for the considered woman, current partner (-1 if none)
|
||||
while (unengaged != -1) { // while there is an unengaged man
|
||||
// unengaged is our proposing man.
|
||||
notAsked = asked[unengaged] + 1;
|
||||
prefWoman = menPrefs[unengaged][notAsked];
|
||||
currentManPartner = womenMatching[prefWoman];
|
||||
// now unengaged asks prefWoman for engagement.
|
||||
asked[unengaged] += 1;
|
||||
if (currentManPartner == -1) { // prefWoman is not engaged: the two engage
|
||||
menMatching[unengaged] = prefWoman;
|
||||
womenMatching[prefWoman] = unengaged;
|
||||
unengaged = getUnengaged(menMatching); // we need a new unengaged
|
||||
} else { // prefWoman is engaged to currentManPartner (therefore >= 0)
|
||||
if (womenRanks[prefWoman][unengaged] < womenRanks[prefWoman][currentManPartner]) {
|
||||
// prefWoman prefers unengaged: split prefWoman and currentManPartner
|
||||
menMatching[currentManPartner] = -1;
|
||||
// and engage prefWoman and unengaged
|
||||
menMatching[unengaged] = prefWoman;
|
||||
womenMatching[prefWoman] = unengaged;
|
||||
unengaged = getUnengaged(menMatching); // we need a new unengaged
|
||||
}
|
||||
// If prefWoman prefers currentManPartner over unengaged, nothing happens
|
||||
// (except that asked[unengaged] has been incremented so unengaged won't ask
|
||||
// prefWoman for engagement anymore).
|
||||
}
|
||||
}
|
||||
return menMatching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a currently unengaged man, if there is one
|
||||
*
|
||||
* @param menMatching the current men matching array (being constructed)
|
||||
* @return the first man that is not engaged, or -1 if all men are engaged
|
||||
*/
|
||||
|
||||
public int getUnengaged(int[] menMatching) {
|
||||
for (int m = 0; m < menMatching.length; m++) {
|
||||
if (menMatching[m] == -1)
|
||||
return m;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
125
src/test/java/com/matchings/stableMatching/GaleShapleyTest.java
Normal file
125
src/test/java/com/matchings/stableMatching/GaleShapleyTest.java
Normal file
@ -0,0 +1,125 @@
|
||||
package src.test.java.com.matchings.stableMatching;
|
||||
|
||||
import src.main.java.com.matchings.stableMatching.GaleShapley;
|
||||
import org.junit.Test;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.Collections; // for shuffling
|
||||
import java.util.ArrayList; // for shuffling
|
||||
import java.util.List; // for shuffling
|
||||
|
||||
public class GaleShapleyTest {
|
||||
|
||||
/**
|
||||
* Test a number of GaleShapley executions on pseudo-random instances of the
|
||||
* stable marriage problem.
|
||||
*/
|
||||
|
||||
@Test
|
||||
public void testGaleShapley() {
|
||||
GaleShapley galeShapley = new GaleShapley();
|
||||
int N = 10;
|
||||
int[][] menPrefs;
|
||||
int[][] womenPrefs;
|
||||
int[] GaleShapleyMenMatching; // the solution returned by GaleShapley.java
|
||||
// for each n from 0 to N-1, create and test an instance of the problem.
|
||||
for (int n = 0; n < N; n++) {
|
||||
System.out.println("testing n = " + n);
|
||||
menPrefs = new int[n][n];
|
||||
womenPrefs = new int[n][n];
|
||||
// set all other sex individuals in each individual's preference list,
|
||||
// then shuffle
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < n; j++) {
|
||||
menPrefs[i][j] = j;
|
||||
womenPrefs[i][j] = j;
|
||||
}
|
||||
shuffleArray(menPrefs[i], i);
|
||||
shuffleArray(womenPrefs[i], n+i);
|
||||
}
|
||||
// Now we have pseudo-random preferences for each man and each woman.
|
||||
GaleShapleyMenMatching = galeShapley.GaleShapleyStableMarriage(menPrefs, womenPrefs);
|
||||
assertEquals("Unstable matching", true, isStable(GaleShapleyMenMatching, menPrefs, womenPrefs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the proposed menMatching is stable, i.e. if there is no
|
||||
* potential couple in which both members would strictly prefer being with each
|
||||
* other than being with their current partner.
|
||||
*
|
||||
* @param menMatching
|
||||
* @param menPrefs
|
||||
* @param womenPrefs
|
||||
* @return whether menMatching is stable according to menPrefs and womenPrefs
|
||||
*/
|
||||
|
||||
public boolean isStable(int[] menMatching, int[][] menPrefs, int[][] womenPrefs) {
|
||||
int n = menMatching.length;
|
||||
// reconstruct womenMatching (for each woman, the associated man):
|
||||
int[] womenMatching = new int[n];
|
||||
int man;
|
||||
int woman;
|
||||
for (man = 0; man < n; man++) {
|
||||
woman = menMatching[man];
|
||||
womenMatching[woman] = man;
|
||||
}
|
||||
|
||||
// construct menRanks and womenRanks to quickly compare preferences:
|
||||
int[][] menRanks = new int[n][n];
|
||||
int[][] womenRanks = new int[n][n];
|
||||
int individualAtThisRank;
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int rank = 0; rank < n; rank++) {
|
||||
// womenRanks
|
||||
individualAtThisRank = womenPrefs[i][rank];
|
||||
womenRanks[i][individualAtThisRank] = rank;
|
||||
|
||||
// menRanks
|
||||
individualAtThisRank = menPrefs[i][rank];
|
||||
menRanks[i][individualAtThisRank] = rank;
|
||||
}
|
||||
}
|
||||
|
||||
// Do the actual test by considering all potential n*n couples and verifying
|
||||
// that at least one of them is happier now than they
|
||||
// would be in the potential couple
|
||||
|
||||
int currentEngagedMan; // man currently engaged to considered woman
|
||||
int currentEngagedWoman; // woman currently engaged to considered man
|
||||
for (man = 0; man < n; man++) {
|
||||
for (woman = 0; woman < n; woman++) {
|
||||
currentEngagedMan = womenMatching[woman];
|
||||
currentEngagedWoman = menMatching[man];
|
||||
if (womenRanks[woman][man] < womenRanks[woman][currentEngagedMan]
|
||||
&& menRanks[man][woman] < menRanks[man][currentEngagedWoman]) {
|
||||
// man prefers woman over currentEngagedWoman, and
|
||||
// woman prefers man over currentEngagedMan.
|
||||
// The marriage therefore isn't stable.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle an array using Collections.shuffle
|
||||
*
|
||||
* @param array array to be shuffled
|
||||
* @param seed fixed seed, for reproducibility
|
||||
*/
|
||||
|
||||
public void shuffleArray(int[] array, long seed) {
|
||||
List<Integer> list = new ArrayList<>();
|
||||
for (int i : array) {
|
||||
list.add(i);
|
||||
}
|
||||
Collections.shuffle(list, new Random(seed));
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
array[i] = list.get(i);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user