From a6bf2ac969fcd5a82ad2a2f4517d09b9b7e56ce0 Mon Sep 17 00:00:00 2001 From: Robin Kuck Date: Tue, 5 Feb 2019 14:19:31 +0100 Subject: [PATCH] bug4469: reimplementation of competitor allocation improvement where boat change factor is used --- .../PairingListCreationSetupDialog.java | 24 +- .../impl/PairingListTemplateImpl.java | 229 +++++++----------- 2 files changed, 98 insertions(+), 155 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationSetupDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationSetupDialog.java index 7ea6ee936eb..09a657f7b86 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationSetupDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/PairingListCreationSetupDialog.java @@ -39,10 +39,8 @@ public class PairingListCreationSetupDialog extends AbstractPairingListCreationS public PairingListCreationSetupDialog(StrippedLeaderboardDTO leaderboardDTO, StringMessages stringMessages, DialogCallback callback) { - super(leaderboardDTO, stringMessages.pairingList(), stringMessages, new PairingListParameterValidator(stringMessages), callback); - this.competitorCountTextBox = createIntegerBox(leaderboardDTO.competitorsCount, 2); this.competitorCountTextBox.addValueChangeHandler(evt -> { updateBoatChangeFactorSelection(leaderboardDTO.getRaceList().get(0).getFleets().size()); @@ -133,16 +131,21 @@ public class PairingListCreationSetupDialog extends AbstractPairingListCreationS } else { dto.setFlightMultiplier(1); } - List selectedFlightNames = new ArrayList<>(); - for (CheckBox box : getCheckedSelectedCheckBoxes()) { - selectedFlightNames.addAll(getRaceColumnNamesFromSeriesName(box.getText(), leaderboardDTO.getRaceList())); - } + List selectedFlightNames = getSelectedFlightNames(); dto.setSelectedFlightNames(selectedFlightNames); dto.setFlightCount(selectedFlightNames.size()); dto.setTolerance(Integer.parseInt(boatChangeFactorListBox.getSelectedValue())); return dto; } + private List getSelectedFlightNames() { + List selectedFlightNames = new ArrayList<>(); + for (CheckBox box : getCheckedSelectedCheckBoxes()) { + selectedFlightNames.addAll(getRaceColumnNamesFromSeriesName(box.getText(), leaderboardDTO.getRaceList())); + } + return selectedFlightNames; + } + public void setDefaultCompetitorCount(final int competitorCount) { if (this.competitorCountTextBox.getValue() == 0) { this.competitorCountTextBox.setValue(competitorCount); @@ -165,15 +168,14 @@ public class PairingListCreationSetupDialog extends AbstractPairingListCreationS } } } else { - if (Util.size(getCheckedSelectedCheckBoxes()) > 0) { - - } else { + if (Util.size(getCheckedSelectedCheckBoxes()) <= 0) { enableOrDisableAllSelectedSeriesCheckBoxes(true, false); } if (Util.size(getCheckedSelectedCheckBoxes()) < 2) { flightMultiplierCheckBox.setEnabled(true); } } + updateBoatChangeFactorSelection(getSelectedFlightNames().size()); } private Iterable getSeriesNamesFromAllRaces(final Iterable raceColumns) { @@ -252,9 +254,9 @@ public class PairingListCreationSetupDialog extends AbstractPairingListCreationS } } - private void updateBoatChangeFactorSelection(final int fleetCount) { + private void updateBoatChangeFactorSelection(final int flightCount) { this.boatChangeFactorListBox.clear(); - IntStream.range(0, (getCompetitorCountInput() / fleetCount) + 1).forEach(i -> { + IntStream.range(0, (getCompetitorCountInput() / flightCount) + 1).forEach(i -> { this.boatChangeFactorListBox.addItem(String.valueOf(i)); }); } diff --git a/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java b/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java index 8963e0c5082..9bd4fb50b98 100644 --- a/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java +++ b/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java @@ -45,11 +45,20 @@ public class PairingListTemplateImpl implements PairingListTemplate { private final Random random = new Random(); private final int[][] pairingListTemplate; private final double standardDev, boatAssignmentsQuality; - private final int flightMultiplier, boatChangeFactor, boatchanges; + private final int flightMultiplier; + + /** + * A tolerance threshold specifying how much deviation between the best (minimum) allocation count and the + * allocation count of the boat used in previous flight is considered acceptable. Example: If the best boat + * has been used only once and the boat used in the previous flight has already been used three times, this + * number has to be greater than (3-1)=2 to allow the competitor to be allocated the same boat as in the + * previous flight. + */ + private final int boatChangeFactor; + private final int boatchanges; private final int dummies; private final ExecutorService executorService = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor(); private final int iterations; - private final int COMPETITOR_ALLOCATION_ITERATIONS = 10; public PairingListTemplateImpl(PairingFrameProvider pairingFrameProvider) { // setting iterations to default of 100.000 @@ -548,66 +557,94 @@ public class PairingListTemplateImpl implements PairingListTemplate { /** * Switches competitors inside a group to improve competitor allocations. Method does not change the order of groups - * or flights. The standard deviation of team associations will not be influenced by this method. After executing - * the method the assignment should be well distributed. While finding a good distribution, the - * {@link #boatChangeFactor} controls how much boat assignment quality to sacrifice in favor of the next step, aming - * to reduce the number of boat changes. + * or flights. The standard deviation of team associations will not be influenced by this method.

+ * + * As the allocations are defined, they are counted. A competitor will be allocated to a boat ideally based on + * minimum count so far. However, if a tolerance for reducing the number of boat changes has been allowed for, + * less than optimal boats can be selected, given the count deviates less than the tolerance from the optimum. + * The tolerance is defined by {@link #boatChangeFactor}. * * @param pairingList * current pairing list, improved in place by this method - * @param flights + * @param numberOflights * count of flights - * @param groups + * @param numberOfGroups * count of groups - * @param competitors + * @param numberOfCompetitors * count of competitors */ - private void improveCompetitorAllocations(int[][] pairingList, int flights, int groups, int competitors) { + private void improveCompetitorAllocations(int[][] pairingList, int numberOflights, int numberOfGroups, int numberOfCompetitors) { + final int numberOfBoats = numberOfCompetitors / numberOfGroups; // 1. Index: competitors, 2. Index: boats; Values represent the number of assignment of a competitor to a boat - int[][] assignments = this.getBoatAssignments(pairingList, new int[competitors][competitors / groups]); - final int numberOfBoats = competitors / groups; - double averageAssignments = flights / numberOfBoats; - for (int iteration = 0; iteration < COMPETITOR_ALLOCATION_ITERATIONS; iteration++) { - for (int raceIndex = 0; raceIndex < pairingList.length; raceIndex++) { - int[][] groupAssignments = new int[numberOfBoats][numberOfBoats]; - final int prevFlight = (int) (raceIndex / groups) - 1; - for (int competitorSlotIndex = 0; competitorSlotIndex < numberOfBoats; competitorSlotIndex++) { - // select single group to optimize - System.arraycopy(assignments[pairingList[raceIndex][competitorSlotIndex]], 0, groupAssignments[competitorSlotIndex], 0, - numberOfBoats); + int[][] boatAllocationCount = new int[numberOfCompetitors][numberOfBoats]; + for (int raceIndex = 0; raceIndex < pairingList.length; raceIndex++) { + final int currentFlightIndex = raceIndex / numberOfGroups; + final int previousFlight = currentFlightIndex - 1; + // records the competitor index at the boat index to which the competitor shall be assigned in the current race + int[] targetAssignmentsForOriginalCompetitorSlots = new int[numberOfBoats]; + for (int i=0; i= 0) { - // search for previous position - for (int raceInPreviousFlightIndex = prevFlight * groups; raceInPreviousFlightIndex < prevFlight * groups + groups; raceInPreviousFlightIndex++) { - for (int boatIndex = 0; boatIndex < numberOfBoats; boatIndex++) { - if (pairingList[raceInPreviousFlightIndex][boatIndex] == pairingList[raceIndex][competitorSlotIndexAndBoatIndex[0]]) { - prevPosition = boatIndex; - break; - } - } + assert boatIndexOfAvailableBoatWithMinimumAllocationCount != -1; + if (currentFlightIndex > 0) { + int boatIndexOfCurrentCompetitorInPreviousFlight = getBoatIndexInFlight(pairingList, numberOfGroups, competitorIndex, previousFlight); + // check whether the boat that the competitor was allocated for the previous flight is still available in this flight: + if (targetAssignmentsForOriginalCompetitorSlots[boatIndexOfCurrentCompetitorInPreviousFlight] == -1) { + // find out how much worse it would be compared to selecting the boat with the minimum allocation count + // if we placed the current competitor onto the same boat as in the previous flight: + int allocationCountDeviationForBoatOfPreviousFlight = allocationCountPerBoatForCurrentCompetitor[boatIndexOfCurrentCompetitorInPreviousFlight] - + minimumAllocationCount; + if (allocationCountDeviationForBoatOfPreviousFlight < boatChangeFactor) { + targetAssignmentsForOriginalCompetitorSlots[boatIndexOfCurrentCompetitorInPreviousFlight] = competitorIndex; + } else { + // it would be too expensive boat-allocation-distribution-wise to place the current competitor onto the + // boat used in the previous flight; allocate the competitor to the first available boat that has the + // least allocation count for that competitor so far: + targetAssignmentsForOriginalCompetitorSlots[boatIndexOfAvailableBoatWithMinimumAllocationCount] = competitorIndex; } - } - if (groupAssignments[competitorSlotIndexAndBoatIndex[0]][competitorSlotIndexAndBoatIndex[1]] > averageAssignments - 1 - && groupAssignments[competitorSlotIndexAndBoatIndex[0]][competitorSlotIndexAndBoatIndex[1]] < averageAssignments + 1) { - // no more optimizations possible - break; } else { - int temp = 0; - int bestPosition = getBestPositionToChangeTo(groupAssignments[competitorSlotIndexAndBoatIndex[0]], prevPosition); - temp = pairingList[raceIndex][bestPosition]; - pairingList[raceIndex][bestPosition] = pairingList[raceIndex][competitorSlotIndexAndBoatIndex[0]]; - pairingList[raceIndex][competitorSlotIndexAndBoatIndex[0]] = temp; - assignments = getBoatAssignments(pairingList, new int[competitors][competitors / groups]); - for (int x = 0; x < numberOfBoats; x++) { - System.arraycopy(assignments[pairingList[raceIndex][x]], 0, groupAssignments[x], 0, numberOfBoats); - } + // the boat used in the previous flight by the current competitor is not available anymore in this flight; + // assign to the least used boat available: + targetAssignmentsForOriginalCompetitorSlots[boatIndexOfAvailableBoatWithMinimumAllocationCount] = competitorIndex; } + } else { // we're in the first flight (index 0), so we cannot consider any boat allocation of any previous flight + targetAssignmentsForOriginalCompetitorSlots[boatIndexOfAvailableBoatWithMinimumAllocationCount] = competitorIndex; + } + } + // now targetAssignmentsForOriginalCompetitorSlots holds the desired assignments for the current race; + // adjust pairing list and update boatAllocationCount: + for (int competitorSlotIndex = 0; competitorSlotIndex < numberOfBoats; competitorSlotIndex++) { + pairingList[raceIndex][competitorSlotIndex] = targetAssignmentsForOriginalCompetitorSlots[competitorSlotIndex]; + boatAllocationCount[targetAssignmentsForOriginalCompetitorSlots[competitorSlotIndex]][competitorSlotIndex]++; + } + } + } + + private int getBoatIndexInFlight(int[][] pairingList, int numberOfGroups, int competitorIndex, int previousFlight) { + for (int raceIndex=previousFlight*numberOfGroups; raceIndex 0 && bestMatchesIndexInPreviousFlight < numberOfGroupsPerFlight - 1) { int[] temp = pairingList[(flightIndex - 1) * numberOfGroupsPerFlight + numberOfGroupsPerFlight - 1]; pairingList[(flightIndex - 1) * numberOfGroupsPerFlight + numberOfGroupsPerFlight - 1] = pairingList[(flightIndex - 1) * numberOfGroupsPerFlight + bestMatchesIndexInPreviousFlight]; pairingList[(flightIndex - 1) * numberOfGroupsPerFlight + bestMatchesIndexInPreviousFlight] = temp; @@ -695,102 +732,6 @@ public class PairingListTemplateImpl implements PairingListTemplate { return result; } - /** - * There is an neededAssigments value created in improveCompetitorAllocations. This value represents - * the number of times a competitor has to be on one boat to get the best possible competitor allocation. In this - * method this value is used to find the competitor which assignment is the worst. That means the number of - * assignments to one special boat is far to low or far to high. This can be calculated by: absolute value of given - * assignment - needed assignment. - * - * @param groupAssignments - * 1. Index: competitor slot in group, 2. Index: boats; Values represent the number of assignment of a - * competitor to a boat - * @param neededAssigments - * reference value - * @return int array with 2 indices that represent competitor slot and boat index of the worst value in the two - * dimensional array groupAssignments - */ - private int[] findWorstValuePosition(int[][] groupAssignments, int neededAssigments) { - int[] worstDeviationPos = new int[2]; - int worstDeviation = 0; - //search the hole groupAssignments array for worst value - for (int competitorSlotIndex = 0; competitorSlotIndex < groupAssignments.length; competitorSlotIndex++) { - for (int boatIndex = 0; boatIndex < groupAssignments[0].length; boatIndex++) { - if (groupAssignments[competitorSlotIndex][boatIndex] >= 0) { - if (Math.abs(groupAssignments[competitorSlotIndex][boatIndex]-neededAssigments) > worstDeviation) { - worstDeviationPos[0] = competitorSlotIndex; - worstDeviationPos[1] = boatIndex; - worstDeviation = Math.abs(groupAssignments[competitorSlotIndex][boatIndex]-neededAssigments); - } - } - } - } - return worstDeviationPos; - } - - /** - * So far competitors have been allocated to the positions (e.g., boats) in a round such that this results in a good - * distribution of competitor-to-position assignments, so that ideally each competitor competes in each position an - * equal number of times. - *

- * - * There is, however, another criterion to watch for: in-between flights it may be useful if competitors - * participating in the last group of the previous flight and the first group of the next flight keep their - * position. If the positions are, e.g., boats, this would mean that such a competitor doesn't need to be shuttled - * from one boat to another, simplifying logistics between flights. - *

- * - * These two criteria (spreading competitors equally across positions vs. letting competitors keep their position at - * flight boundaries) contradict each other, obviously. Leaving a competitor in the same position across two rounds - * reduces the spread of the competitor-to-position distribution. - *

- * - * The {@link #boatChangeFactor} is used to express a balance between these two criteria. It may range between - * {@code 0..#competitors/#groups}. - *

- * - * This method searches for the position a competitor should be changed to, to improve the competitor allocations as - * much as possible. The decision depends on the field {@link #boatChangeFactor}. If this value is 0, the method - * returns the position on which a competitor is too rarely registered and should be changed to to get a better - * competitor allocation. - * - * If the value is greater than 0, the method can take the allocation in the previous flight into consideration. The - * value represents a kind of tolerance to deviate from the best possible value to place the competitor on a - * position, which he was on in the last group. The method starts at the best position and proofs if the competitor - * on this position in the last group if yes return this position, if not go on with the next position. This is done - * as often as the tolerance allows it. If no position is found the best position to change to is returned - * regardless of the position in the last flight. This is necessary if you would like to minimize boat changes at - * the expense of competitor allocations. - * - * @param competitorAllocations - * array that contains the current competitor allocations for one group; index is the position (or boat) - * number - * @param previousPosition - * position of the competitor that need to be changed in the previous group - * @return position the competitor should be changed to - */ - private int getBestPositionToChangeTo(int[] competitorAllocations, int previousPosition) { - int[] temp = new int[competitorAllocations.length]; - System.arraycopy(competitorAllocations, 0, temp, 0, competitorAllocations.length); - Arrays.sort(temp); - // search for best position to change to with consideration of previous position - final int iterations = Math.min(boatChangeFactor, competitorAllocations.length); - for (int i = 0; i < iterations; i++) { - for (int position = 0; position < competitorAllocations.length; position++) { - if (temp[i] == competitorAllocations[position] && position == previousPosition) { - return position; - } - } - } - // search for best position to change to regardless of previous position - for (int position = 0; position < competitorAllocations.length; position++) { - if (temp[0] == competitorAllocations[position]) { - return position; - } - } - return -1; - } - /** * Returns a random number between min and max. *