introduced raceColumnsToConsider through the leaderboard scoring APIs to prepare for leaderboard rank chart

This commit is contained in:
Axel Uhl
2012-11-16 13:51:15 +01:00
parent 11543e4fb7
commit 0d579745be
6 changed files with 82 additions and 13 deletions
@@ -98,6 +98,22 @@ public interface Leaderboard extends Named {
Entry getEntry(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException;
/**
* Computes the competitor's ranks as they were or would have been after each race column (from left to right)
* was completed.<p>
*
* A leaderboard fills up over time, usually "from left to right" with one race after another finishing.
* For split fleets things can vary slightly. There, one fleet may complete a few races before the another fleet
* starts with those races. In this case there isn't even any point in time at which all fleets have finished
* exactly <i>n</i> races. Still, this method pretends such a time point would have existed, actually ignoring
* the <i>times</i> at which a race took place but only looking at the resulting scores and discard.<p>
*
* When computing the ranks after all columns up to and including the race column that is the key of the resulting
* map, the method applies the discarding and tie breaking rules as they would have had to be applied had the races
* in the respective column just completed.
*/
Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException;
/**
* Tells the number of points carried over from previous races not tracked by this leaderboard for
* the <code>competitor</code>. Returns <code>0</code> if there is no carried points definition for
@@ -201,7 +217,7 @@ public interface Leaderboard extends Named {
* Fetches all entries for all competitors of all races tracked by this leaderboard in one sweep. This saves some
* computational effort compared to fetching all entries separately, particularly because all
* {@link #isDiscarded(Competitor, RaceColumn, TimePoint) discarded races} of a competitor are computed in one
* sweep using {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, TimePoint)} only once.
* sweep using {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)} only once.
* Note that in order to get the {@link #getTotalPoints(Competitor, TimePoint) total points} for a competitor
* for the entire leaderboard, the {@link #getCarriedPoints(Competitor) carried-over points} need to be added.
*/
@@ -16,5 +16,10 @@ import com.sap.sailing.domain.common.TimePoint;
*
*/
public interface ResultDiscardingRule extends Serializable {
Set<RaceColumn> getDiscardedRaceColumns(Competitor competitor, Leaderboard leaderboard, TimePoint timePoint);
/**
* @param raceColumnsToConsider if a column is <code>not</code> contained, its existence will be ignored for determining the
* columns to discard. It affects the count of races. Only columns contained can be part of the result.
*/
Set<RaceColumn> getDiscardedRaceColumns(Competitor competitor, Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint);
}
@@ -19,6 +19,9 @@ public interface SettableScoreCorrection extends ScoreCorrection {
*/
void setMaxPointsReason(Competitor competitor, RaceColumn raceColumn, MaxPointsReason reason);
/**
* @return a non-<code>null</code> result
*/
MaxPointsReason getMaxPointsReason(Competitor competitor, RaceColumn raceColumn);
void correctScore(Competitor competitor, RaceColumn raceColumn, double points);
@@ -214,13 +214,22 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) {
return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint);
}
private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) {
return !raceColumn.isMedalRace() && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable()
&& getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, timePoint).contains(
&& getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint).contains(
raceColumn);
}
@Override
public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException {
return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint);
}
private Double getTotalPoints(Competitor competitor, RaceColumn raceColumn,
Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException {
Double result;
if (isDiscarded(competitor, raceColumn, timePoint)) {
result = 0.0;
@@ -298,16 +307,20 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
@Override
public List<Competitor> getCompetitorsFromBestToWorst(TimePoint timePoint) throws NoWindException {
return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint);
}
private List<Competitor> getCompetitorsFromBestToWorst(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException {
List<Competitor> result = new ArrayList<Competitor>();
for (Competitor competitor : getCompetitors()) {
result.add(competitor);
}
Collections.sort(result, getTotalRankComparator(timePoint));
Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint));
return result;
}
protected Comparator<? super Competitor> getTotalRankComparator(TimePoint timePoint) throws NoWindException {
return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false);
protected Comparator<? super Competitor> getTotalRankComparator(Iterable<RaceColumn> raceColumnsToConsider, TimePoint timePoint) throws NoWindException {
return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider);
}
@Override
@@ -368,6 +381,17 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor));
}
@Override
public Map<RaceColumn, List<Competitor>> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException {
Map<RaceColumn, List<Competitor>> result = new HashMap<>();
List<RaceColumn> raceColumnsToConsider = new ArrayList<>();
for (RaceColumn raceColumn : getRaceColumns()) {
raceColumnsToConsider.add(raceColumn);
result.put(raceColumn, getCompetitorsFromBestToWorst(raceColumnsToConsider, timePoint));
}
return result;
}
@Override
public Map<Pair<Competitor, RaceColumn>, Entry> getContent(final TimePoint timePoint) throws NoWindException {
Map<Pair<Competitor, RaceColumn>, Entry> result = new HashMap<Pair<Competitor, RaceColumn>, Entry>();
@@ -384,7 +408,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
timePoint, Util.size(getCompetitors()), getScoringScheme());
Set<RaceColumn> discardedRacesForCompetitor = discardedRaces.get(competitor);
if (discardedRacesForCompetitor == null) {
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, timePoint);
discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
discardedRaces.put(competitor, discardedRacesForCompetitor);
}
boolean discarded = discardedRacesForCompetitor.contains(raceColumn);
@@ -52,8 +52,27 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
private final boolean nullScoresAreBetter;
private final TimePoint timePoint;
/**
* Considers all of the leaderboard's columns in their state at <code>timePoint</code> for calculating the score and rank.
*/
public LeaderboardTotalRankComparator(Leaderboard leaderboard, TimePoint timePoint,
ScoringScheme scoringScheme, boolean nullScoresAreBetter) throws NoWindException {
this(leaderboard, timePoint, scoringScheme, nullScoresAreBetter, leaderboard.getRaceColumns());
}
/**
* Considers only the race columns specified in <code>raceColumnsToConsider</code> and behaves as if the other columns
* were filled with <code>null</code> values. Those columns not considered do not count for determining the discards either.
* For example, if the first race may be discarded when five races have been completed, and only four {@link RaceColumn}s are
* considered, no race's score will be discarded for this call. This allows clients to tell what the ranking would have been
* with only the race columns specified in <code>raceColumnsToConsider</code> having completed for all fleets.<p>
*
* Note, that <code>timePoint</code> is considered in addition to <code>raceColumnsToConsider</code> such that the scores in
* those columns considered is computed for the <code>timePoint</code> specified. In particular, if a time point is chosen that
* is before a race in a column that is considered has started, <code>null</code> values may result in that column.
*/
public LeaderboardTotalRankComparator(Leaderboard leaderboard, TimePoint timePoint,
ScoringScheme scoringScheme, boolean nullScoresAreBetter, Iterable<RaceColumn> raceColumnsToConsider) throws NoWindException {
super();
this.leaderboard = leaderboard;
this.timePoint = timePoint;
@@ -61,7 +80,7 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
this.nullScoresAreBetter = nullScoresAreBetter;
totalPointsCache = new HashMap<Pair<Competitor, RaceColumn>, Double>();
for (Competitor competitor : leaderboard.getCompetitors()) {
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (RaceColumn raceColumn : raceColumnsToConsider) {
totalPointsCache.put(new Pair<Competitor, RaceColumn>(competitor, raceColumn),
leaderboard.getTotalPoints(competitor, raceColumn, timePoint));
}
@@ -50,8 +50,9 @@ public class ResultDiscardingRuleImpl implements ThresholdBasedResultDiscardingR
}
@Override
public Set<RaceColumn> getDiscardedRaceColumns(final Competitor competitor, final Leaderboard leaderboard, final TimePoint timePoint) {
int resultsToDiscard = getNumberOfResultsToDiscard(competitor, leaderboard.getRaceColumns(), leaderboard, timePoint);
public Set<RaceColumn> getDiscardedRaceColumns(final Competitor competitor, final Leaderboard leaderboard,
Iterable<RaceColumn> raceColumnsToConsider, final TimePoint timePoint) {
int resultsToDiscard = getNumberOfResultsToDiscard(competitor, raceColumnsToConsider, leaderboard, timePoint);
Set<RaceColumn> result;
if (resultsToDiscard > 0) {
result = new HashSet<RaceColumn>();
@@ -72,7 +73,7 @@ public class ResultDiscardingRuleImpl implements ThresholdBasedResultDiscardingR
}
}
};
for (RaceColumn raceColumn : leaderboard.getRaceColumns()) {
for (RaceColumn raceColumn : raceColumnsToConsider) {
if (!raceColumn.isMedalRace()) {
sortedRaces.add(raceColumn);
}
@@ -94,10 +95,11 @@ public class ResultDiscardingRuleImpl implements ThresholdBasedResultDiscardingR
return result;
}
private int getNumberOfResultsToDiscard(Competitor competitor, Iterable<RaceColumn> raceColumns, Leaderboard leaderboard, TimePoint timePoint) {
private int getNumberOfResultsToDiscard(Competitor competitor, Iterable<RaceColumn> raceColumnsToConsider,
Leaderboard leaderboard, TimePoint timePoint) {
int numberOfResultsToDiscard;
int numberOfStartedRaces = 0;
for (RaceColumn raceInLeaderboard : raceColumns) {
for (RaceColumn raceInLeaderboard : raceColumnsToConsider) {
if (leaderboard.countRaceForComparisonWithDiscardingThresholds(competitor, raceInLeaderboard, timePoint)) {
numberOfStartedRaces++;
}