Merge pull request #691 from TimotheeChauvin/Development

add the Gale Shapley algorithm and its test
This commit is contained in:
Libin Yang 2019-01-19 12:48:08 +08:00 committed by GitHub
commit e631e3214b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 228 additions and 0 deletions

View 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;
}
}

View 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);
}
}
}