Skip to content

Commit

Permalink
Fix groups affecting team preferences for subsequent allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
haykam821 authored and Patbox committed Dec 5, 2024
1 parent 5b04486 commit a6b474d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public void allocate(BiConsumer<T, V> apply) {
* @return a {@link Multimap} containing every team and the allocated players
*/
public Multimap<T, V> allocate() {
var allocations = new Allocations<T, V>();
var allocations = new Allocations<T, V>(this.teamPreferences);

// 1. place players evenly and randomly into all the teams
this.placePlayersRandomly(allocations);
Expand Down Expand Up @@ -162,7 +162,7 @@ private void setGroupPreferences(Allocations<T, V> allocations) {

// set team preferences to the group's team if the player has no preference
for (var player : group) {
this.teamPreferences.putIfAbsent(player, team);
allocations.teamPreferences.putIfAbsent(player, team);
}

teamIndex = (teamIndex + 1) % this.teams.size();
Expand All @@ -174,7 +174,7 @@ private void optimizeTeamsByPreference(Allocations<T, V> allocations) {
Collections.shuffle(players);

for (var player : players) {
var preference = this.teamPreferences.get(player);
var preference = allocations.teamPreferences.get(player);
var current = allocations.teamFor(player);

// we have no preference or we are already in our desired position, continue
Expand All @@ -201,19 +201,19 @@ private boolean canTeamGrow(T team, int size) {
}

private void trySwapWithOtherPlayer(Allocations<T, V> allocations, V player, T from, T to) {
var swapWith = this.findSwapCandidate(from, to, allocations.playersIn(to));
var swapWith = this.findSwapCandidate(allocations, from, to, allocations.playersIn(to));
if (swapWith != null) {
allocations.moveTeam(player, from, to);
allocations.moveTeam(swapWith, to, from);
}
}

@Nullable
private V findSwapCandidate(T from, T to, Collection<V> candidates) {
private V findSwapCandidate(Allocations<T, V> allocations, T from, T to, Collection<V> candidates) {
V swapWith = null;

for (var candidate : candidates) {
var candidatePreference = this.teamPreferences.get(candidate);
var candidatePreference = allocations.teamPreferences.get(candidate);
if (candidatePreference == to) {
// we can't swap with this player: they are already in their chosen team
continue;
Expand All @@ -231,6 +231,11 @@ private V findSwapCandidate(T from, T to, Collection<V> candidates) {
static final class Allocations<T, V> {
final Multimap<T, V> teamToPlayers = HashMultimap.create();
final Map<V, T> playerToTeam = new Object2ObjectOpenHashMap<>();
final Map<V, T> teamPreferences = new Object2ObjectOpenHashMap<>();

Allocations(Map<V, T> teamPreferences) {
this.teamPreferences.putAll(teamPreferences);
}

void setTeam(V player, T team) {
this.teamToPlayers.put(team, player);
Expand Down
42 changes: 42 additions & 0 deletions src/test/java/xyz/nucleoid/plasmid/test/TeamAllocatorTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@

import java.util.Collection;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.*;

Expand Down Expand Up @@ -160,6 +164,44 @@ public void groupingAllocation() {
});
}

@Test
public void uniqueGroupsFromMultipleRuns() {
var team1 = new Team("team1");
var team2 = new Team("team2");

var allocator = createAllocator(Set.of(team1, team2));

var players = IntStream.range(0, 20)
.mapToObj(index -> new Player("player" + index))
.collect(Collectors.toList());

for (var player : players) {
allocator.add(player, null);
}

allocator.group(players.subList(0, players.size() / 2));
allocator.group(players.subList(players.size() / 2, players.size()));

var allocations = new HashSet<HashMap<Team, Set<Player>>>();

for (int i = 0; i < REPEAT_COUNT; i++) {
var allocation = allocator.allocate();
var orderedAllocation = new HashMap<Team, Set<Player>>();

for (var entry : allocation.entries()) {
var team = entry.getKey();
var player = entry.getValue();

var teamPlayers = orderedAllocation.computeIfAbsent(team, k -> new HashSet<>());
teamPlayers.add(player);
}

allocations.add(orderedAllocation);
}

assertTrue(allocations.size() > 1, "expected multiple unique allocations, but found " + allocations);
}

@Test
public void cannotAllocateWithNoTeams() {
assertThrows(IllegalArgumentException.class, () -> {
Expand Down

0 comments on commit a6b474d

Please sign in to comment.