/** * First version was taken from the package roboutils.planning * written by Prasanna Velagapudi <psigen@gmail.com>. * * However there was an error in the algorithm which was fixed. * Since the error wasn't fixed in the original package, not even two months * after reporting the issue, it has been moved directly into the ThunderSTORM. * Then the algorithm was slightly changed for the purpose of ThunderSTORM. */ package cz.cuni.lf1.lge.ThunderSTORM.util; import cz.cuni.lf1.lge.ThunderSTORM.estimators.PSF.Molecule; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; /** * Gale-Shapley algorithm */ public class StableMatching { public static <T extends IMatchable> Map<T, T> match(final List<T> suitors) { // Create a free list of suitors (and use it to store their proposals) Queue<MTuple<T>> freeSuitors = new LinkedList<MTuple<T>>(); for (T suitor : suitors) { assert(suitor.getNeighbors() != null) : "Suitors must always have list of prefered reviewers (neighbors)!"; @SuppressWarnings("unchecked") LinkedList<T> prefs = new LinkedList<T>(suitor.getNeighbors()); Collections.sort(prefs, suitorPreference(suitor)); freeSuitors.add(new MTuple<T>(suitor, prefs)); } // Create an initially empty map of engagements Map<T, MTuple<T>> engagements = new HashMap<T, MTuple<T>>(); // As per wikipedia algorithm while (!freeSuitors.isEmpty()) { // The next free suitor who has a reviewer to propose to MTuple<T> m = freeSuitors.peek(); // m's highest ranked such woman who he has not proposed to yet T w = m.prefs.poll(); if(w == null) { freeSuitors.poll(); continue; } // Tf w is free if (!engagements.containsKey(w)) { // (m, w) become engaged engagements.put(w, m); freeSuitors.poll(); } else { // Some pair (m', w) already exists MTuple<T> mPrime = engagements.get(w); if (reviewerPreference(w).compare(mPrime.suitor, m.suitor) < 0) { // (m, w) become engaged engagements.put(w, m); freeSuitors.poll(); // m' becomes free freeSuitors.add(mPrime); } } } // Convert internal data structure to mapping HashMap<T, T> matches = new HashMap<T, T>(); for (Map.Entry<T, MTuple<T>> entry : engagements.entrySet()) matches.put(entry.getValue().suitor, entry.getKey()); return matches; } private static class MTuple<T> { final T suitor; final Queue<T> prefs; public MTuple(T s, Queue<T> p) { suitor = s; prefs = p; } } private static <T extends IMatchable> Comparator<T> suitorPreference(final T suitor) { return new Comparator<T>() { @Override public int compare(T a, T b) { double diff = suitor.getDist2(a) - suitor.getDist2(b); if(diff < 0.0) return -1; if(diff > 0.0) return +1; return 0; } }; } private static <T extends IMatchable> Comparator<T> reviewerPreference(final T reviewer) { return new Comparator<T>() { @Override public int compare(T a, T b) { double diff = reviewer.getDist2(a) - reviewer.getDist2(b); if(diff < 0.0) return -1; if(diff > 0.0) return +1; return 0; } }; } }