'How can I sort using a comparator with ties broken randomly?
I'm building a program that simulates a sports league, and, oftentimes, I want to sort teams by points, goal differential, etc. to determine things like which team gets promoted or which team gets preferential seeding in a tournament. But, sometimes, teams will be tied on all the criteria that I want to use, and I would like those ties to be broken randomly. I was previously using teams' ids (which are unique ints) to break ties, but I'd like to make it so that no team gets consistent preferential treatment in this sorting. I know that if I just let Java decide which of the tied teams will be placed in front of the other, it'll be very unpredictable, but I don't trust it to be actually random, and I'm worried that it'd inadvertently give some teams an advantage.
I've been using Collections.sort() with comparators to do my sorting, and I recently just went for it, and switched my comparators to use a random number as the final tiebreaker rather than the team ids. Here is the comparator in question:
public static final Comparator<Team> pointsGDRand = new Comparator<Team>() {
public int compare(Team a, Team b) {
int result = b.getStats().getPts() - a.getStats().getPts();
if (result == 0) {
result = b.getStats().getGD() - a.getStats().getGD();
if (result == 0) {
result = R.boolNum();
}
}
return result;
}
};
I think the getter functions are self explanatory, but here's that random function:
public static int boolNum() {
return r.nextBoolean() ? 1 : -1;
}
But, just a bit ago (it look a long time for this error to pop up), I got this error:
Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
I've read a little bit, and I think that the problem is likely that I had some instance where it figured out that A > B and B > C, but also A < C. This sheds some light on why it took so long for this error to pop up (it did maybe 20,000 sorts before it popped up with this error), because 2 teams getting the exact same score on all criteria seems like a pretty rare occurrence to me, much less 3 or more.
I'd rather not write my own custom sort, and I'd also rather not shuffle the list in question before each time I sort it (I feel like that'd be inefficient, but I'm confident it would work).
So, how can I make a comparator that breaks ties randomly (such that there's no risk of a team winning tiebreaks disproportionately) without risking that same error again?
Solution 1:[1]
Here's a different approach for randomly choosing tied teams
Use a map.
- store the teams that tied in a map, keyed by their score. The map can be a tree map so it can be sorted.
- then iterate over the map and pick a team at random from each list.
Here are the teams before any processing. The output is score and team name.
[3, A]
[1, B]
[2, C]
[0, D]
[0, E]
[1, F]
[1, G]
[2, H]
[2, I]
[2, J]
Now build the team map.
Map<Integer, List<Team>> team = teams.stream().collect(
Collectors.groupingBy(t->t.getStats().getPts(),
()->new TreeMap<Integer,List<Team>>(Comparator.reverseOrder()),
Collectors.toList()));
Here is the map of the teams, sorted in descending order of score. The key is the score and the value is a list of teams with that score.
3=[[3, A]]
2=[[2, C], [2, H], [2, I], [2, J]]
1=[[1, B], [1, F], [1, G]]
0=[[0, D], [0, E]]
Now, just pick a random value from each list for a given score. This picks a value between 0 and the number of values in the list (i.e. size())
Random r = new Random();
for (Entry<Integer, List<Team>> e : team.entrySet()) {
int pick = r.nextInt(e.getValue().size());
System.out.println(e.getKey() + " " + e.getValue().get(pick));
for this run printed
3 [3, A]
2 [2, C]
1 [1, B]
0 [0, E]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
