eu.lp0.cursus.scoring.scores.impl.AveragingRacePointsData.java Source code

Java tutorial

Introduction

Here is the source code for eu.lp0.cursus.scoring.scores.impl.AveragingRacePointsData.java

Source

/*
   cursus - Race series management program
   Copyright 2012-2014  Simon Arlott
    
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
    
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.
    
   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.lp0.cursus.scoring.scores.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ArrayTable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Table;

import eu.lp0.cursus.db.data.Pilot;
import eu.lp0.cursus.db.data.Race;
import eu.lp0.cursus.db.data.RaceAttendee;
import eu.lp0.cursus.scoring.data.RacePointsData;
import eu.lp0.cursus.scoring.data.Scores;
import eu.lp0.cursus.scoring.scores.base.ForwardingScores;

public class AveragingRacePointsData<T extends Scores> extends GenericRacePointsData<T> {
    private final ScoresBeforeAveraging scoresBeforeAveraging = new ScoresBeforeAveraging();
    private final AveragingMethod method;
    private final RoundingMode rounding;
    protected final Supplier<Table<Pilot, Race, Integer>> lazyRacePointsBeforeAveraging = Suppliers
            .memoize(new Supplier<Table<Pilot, Race, Integer>>() {
                @Override
                public Table<Pilot, Race, Integer> get() {
                    Table<Pilot, Race, Integer> racePoints = ArrayTable.create(scores.getPilots(),
                            scores.getRaces());
                    for (Race race : scores.getRaces()) {
                        racePoints.column(race).putAll(AveragingRacePointsData.super.calculateRacePoints(race));

                        for (Pilot pilot : scores.getSimulatedRacePoints(race)) {
                            if (!getOtherRacesForPilot(pilot, race, false).isEmpty()) {
                                // Null the score so that it can't be discarded
                                racePoints.column(race).put(pilot, null);
                            }
                        }
                    }
                    return racePoints;
                }
            });

    public enum AveragingMethod {
        BEFORE_DISCARDS, AFTER_DISCARDS, SET_NULL;
    }

    public AveragingRacePointsData(T scores, FleetMethod fleetMethod, AveragingMethod averagingMethod,
            RoundingMode rounding) {
        super(scores, fleetMethod);

        this.method = averagingMethod;
        this.rounding = rounding;
    }

    public AveragingRacePointsData(T scores, FleetMethod raceFleetMethod, FleetMethod nonAttendeeFleetMethod,
            AveragingMethod averagingMethod, RoundingMode rounding) {
        super(scores, raceFleetMethod, nonAttendeeFleetMethod);

        this.method = averagingMethod;
        this.rounding = rounding;
    }

    @Override
    protected boolean calculateSimulatedRacePoints(Pilot pilot, Race race) {
        // Return true even if the points aren't actually simulated but would
        // be simulated if there were more races to calculate an average from
        RaceAttendee attendee = pilot.getRaces().get(race);
        return attendee != null && attendee.getType().isMandatory();
    }

    // For each pilot with simulated races, this is called twice:
    // 1. When calculating points before averaging (!checkDiscards)
    // 2. When calculating averaged points (checkDiscards)
    //
    // An optimal implementation for pilots with simulated points
    // in many races would calculate for all of the pilot's races
    // together - but this is an very unusual scenario and relatively
    // few simulated points need to be calculated for each race
    private Set<Race> getOtherRacesForPilot(Pilot pilot, Race race, boolean checkDiscards) {
        Set<Race> otherRaces = new HashSet<Race>(scores.getRaces().size() * 2);

        // Find other races where the pilot is not attending in a mandatory position
        for (Race otherRace : Iterables.filter(scores.getRaces(), Predicates.not(Predicates.equalTo(race)))) {
            if (!scores.hasSimulatedRacePoints(pilot, otherRace)) {
                otherRaces.add(otherRace);
            }
        }

        // If averaging should occur after discards, remove discards... unless that removes all races
        if (checkDiscards && !otherRaces.isEmpty() && method == AveragingMethod.AFTER_DISCARDS) {
            Set<Race> discardedRaces = scoresBeforeAveraging.getDiscardedRaces(pilot);
            if (!discardedRaces.containsAll(otherRaces)) {
                otherRaces.removeAll(discardedRaces);
            }
        }

        return otherRaces;
    }

    @Override
    protected Map<Pilot, Integer> calculateRacePoints(Race race) {
        Table<Pilot, Race, Integer> racePoints = ArrayTable.create(scoresBeforeAveraging.getRacePoints());

        if (method != AveragingMethod.SET_NULL) {
            for (Pilot pilot : scores.getPilots()) {
                // Calculate an average score using the other races
                if (racePoints.row(pilot).get(race) == null) {
                    Set<Race> otherRaces = getOtherRacesForPilot(pilot, race, true);

                    // Add the scores from the other races
                    int points = 0;
                    for (Race otherRace : otherRaces) {
                        points += racePoints.row(pilot).get(otherRace);
                    }

                    // Calculate and apply the average
                    points = BigDecimal.valueOf(points).divide(BigDecimal.valueOf(otherRaces.size()), rounding)
                            .intValue();
                    racePoints.row(pilot).put(race, points);
                }
            }
        } else {
            // Do nothing, the scores for those pilots will be null
        }

        return racePoints.column(race);
    }

    public class ScoresBeforeAveraging extends ForwardingScores {
        private final Supplier<RacePointsData> racePointsData = Suppliers.memoize(new Supplier<RacePointsData>() {
            @Override
            public RacePointsData get() {
                return new RacePointsData() {
                    @Override
                    public Table<Pilot, Race, Integer> getRacePoints() {
                        return lazyRacePointsBeforeAveraging.get();
                    }

                    @Override
                    public int getRacePoints(Pilot pilot, Race race) {
                        return lazyRacePointsBeforeAveraging.get().get(pilot, race);
                    }

                    @Override
                    public Map<Race, Integer> getRacePoints(Pilot pilot) {
                        return lazyRacePointsBeforeAveraging.get().row(pilot);
                    }

                    @Override
                    public Map<Pilot, Integer> getRacePoints(Race race) {
                        return lazyRacePointsBeforeAveraging.get().column(race);
                    }

                    @Override
                    public Map<Race, ? extends Set<Pilot>> getSimulatedRacePoints() {
                        return AveragingRacePointsData.this.getSimulatedRacePoints();
                    }

                    @Override
                    public Map<Pilot, ? extends Set<Race>> getSimulatedPilotPoints() {
                        return AveragingRacePointsData.this.getSimulatedPilotPoints();
                    }

                    @Override
                    public boolean hasSimulatedRacePoints(Pilot pilot, Race race) {
                        return AveragingRacePointsData.this.hasSimulatedRacePoints(pilot, race);
                    }

                    @Override
                    public Set<Race> getSimulatedRacePoints(Pilot pilot) {
                        return AveragingRacePointsData.this.getSimulatedRacePoints(pilot);
                    }

                    @Override
                    public Set<Pilot> getSimulatedRacePoints(Race race) {
                        return AveragingRacePointsData.this.getSimulatedRacePoints(race);
                    }

                    @Override
                    public int getFleetSize(Race race) {
                        return AveragingRacePointsData.this.getFleetSize(race);
                    }
                };
            }
        });

        @Override
        protected Scores delegate() {
            return scores;
        }

        /**
         * Provide an implementation of {@code RacePointsData} that returns the points before averaging but uses {@code this} instance to do so (this is
         * important as {@code AveragingRacePointsData} could be sub-classed)
         */
        @Override
        protected RacePointsData delegateRacePointsData() {
            return racePointsData.get();
        }
    }
}