Java tutorial
/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package de.appsolve.padelcampus.utils; import de.appsolve.padelcampus.comparators.GameByStartDateComparator; import de.appsolve.padelcampus.constants.Gender; import de.appsolve.padelcampus.data.ScoreEntry; import de.appsolve.padelcampus.db.dao.*; import de.appsolve.padelcampus.db.model.*; import org.apache.log4j.Logger; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.*; import java.util.stream.StreamSupport; /** * @author dominik * see https://metinmediamath.wordpress.com/2013/11/27/how-to-calculate-the-elo-rating-including-example/ */ @Component @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class RankingUtil { private static final Logger LOG = Logger.getLogger(RankingUtil.class); private static final int ELO_MAX_DAYS = 365; private static final BigDecimal ELO_K_FACTOR = new BigDecimal("32"); private static final BigDecimal ELO_MAGIC_NUMBER = new BigDecimal("400"); @Autowired GameDAOI gameDAO; @Autowired TeamDAOI teamDAO; @Autowired RankingDAOI rankingDAO; @Autowired CustomerDAOI customerDAO; @Autowired GameBaseDAOI gameBaseDAO; @Autowired RankingBaseDAOI rankingBaseDAO; public List<Ranking> getTeamRanking(Gender gender, LocalDate date) { if (date == null) { date = LocalDate.now(); } List<Ranking> rankings = getRanking(gender, date); List<Game> games = getGamesInLastYearSince(gender, date); Set<Team> teams = new HashSet<>(); for (Game game : games) { Set<Participant> participants = game.getParticipants(); for (Participant p : participants) { if (p instanceof Team) { teams.add((Team) p); } } } return getTeamRanking(gender, rankings, teams, date); } public List<Ranking> getTeamRanking(Gender gender, Collection<Team> teams, LocalDate date) { List<Ranking> rankings = getRanking(gender, date); return getTeamRanking(gender, rankings, teams, date); } public List<Ranking> getRanking(Gender gender, LocalDate date) { if (date == null) { date = LocalDate.now(); } List<Ranking> rankings = rankingDAO.findByGenderAndDate(gender, date); if (rankings == null || rankings.isEmpty()) { rankings = updateRanking(gender, date); } rankings.forEach(r -> { r.setValue(r.getValue().setScale(0, RoundingMode.HALF_UP)); }); return rankings; } public void updateRanking() { for (Customer customer : customerDAO.findAll()) { LocalDate date = LocalDate.now(); for (Gender gender : Gender.values()) { try { List<Ranking> existingRankings = rankingBaseDAO.findByGenderAndDate(gender, date, customer); LOG.info(String.format( "updating ranking for [customer: %s, gender: %s, date: %s, ranking count: %s] - start", customer, gender, date.toString(), existingRankings.size())); List<Game> games = getGamesInLastYearSince(gender, date, customer); List<Ranking> rankings = getRanking(games, gender, date); rankings.forEach(r -> r.setCustomer(customer)); rankingBaseDAO.delete(existingRankings); rankingBaseDAO.saveOrUpdate(rankings); LOG.info(String.format( "updating ranking for [customer: %s, gender: %s, date: %s, ranking count: %s] - end", customer, gender, date.toString(), rankings.size())); } catch (Exception e) { LOG.error(e, e); } System.gc(); } System.gc(); } } private List<Ranking> updateRanking(Gender gender, LocalDate date) { try { LOG.info(String.format("updating ranking for [gender: %s, date: %s] - start", gender, date.toString())); List<Game> games = getGamesInLastYearSince(gender, date); List<Ranking> rankings = getRanking(games, gender, date); List<Ranking> existingRankings = rankingDAO.findByGenderAndDate(gender, date); rankingDAO.delete(existingRankings); rankingDAO.saveOrUpdate(rankings); LOG.info(String.format("updating ranking for [gender: %s, date: %s] - end", gender, date.toString())); return rankings; } catch (Exception e) { LOG.error(e, e); return null; } } public List<Ranking> getPlayerRanking(Gender gender, Collection<Player> participants, final LocalDate date) { List<Ranking> rankings = getRanking(gender, date); List<Ranking> eventRanking = new ArrayList<>(); rankings.forEach(ranking -> { Participant participant = ranking.getParticipant(); if (participant instanceof Player) { Player player = (Player) participant; if (participants.contains(player)) { eventRanking.add(ranking); } } }); participants.forEach(participant -> { boolean participantRankingExists = StreamSupport.stream(eventRanking.spliterator(), false) .anyMatch(ranking -> ranking.getParticipant().equals(participant)); if (!participantRankingExists) { Ranking ranking = new Ranking(participant, gender, participant.getInitialRankingAsBigDecimal(), date); eventRanking.add(ranking); } }); Collections.sort(eventRanking); return eventRanking; } public List<Ranking> getPlayerRanking(Collection<Player> players, LocalDate date) { Set<Player> malePlayers = new HashSet<>(); Set<Player> femalePlayers = new HashSet<>(); for (Player player : players) { if (player.getGender().equals(Gender.male)) { malePlayers.add(player); } else { femalePlayers.add(player); } } List<Ranking> rankings = getPlayerRanking(Gender.male, malePlayers, date); rankings.addAll(getPlayerRanking(Gender.female, femalePlayers, date)); Collections.sort(rankings); return rankings; } public List<Ranking> getPlayerRanking(Gender gender, Participant participant, LocalDate startDate, LocalDate endDate) { return rankingDAO.findByParticipantBetweenDates(participant, gender, startDate, endDate); } public List<Ranking> getTeamRanking(Collection<Team> teams, LocalDate date) { Set<Team> maleTeams = new HashSet<>(); Set<Team> femaleTeams = new HashSet<>(); Set<Team> mixedTeams = new HashSet<>(); for (Team team : teams) { Set<Gender> teamGender = new HashSet<>(); for (Player player : team.getPlayers()) { teamGender.add(player.getGender()); } if (teamGender.size() > 1) { mixedTeams.add(team); } else if (teamGender.iterator().next().equals(Gender.male)) { maleTeams.add(team); } else { femaleTeams.add(team); } } List<Ranking> ranking = getTeamRanking(Gender.male, maleTeams, date); ranking.addAll(getTeamRanking(Gender.female, femaleTeams, date)); ranking.addAll(getTeamRanking(Gender.mixed, mixedTeams, date)); Collections.sort(ranking); return ranking; } public List<Ranking> getRankedParticipants(Event model) { List<Ranking> ranking = new ArrayList<>(); if (!model.getParticipants().isEmpty()) { Participant firstParticipant = model.getParticipants().iterator().next(); if (firstParticipant instanceof Player) { ranking = getPlayerRanking(model.getGender(), model.getPlayers(), LocalDate.now()); } else if (firstParticipant instanceof Team) { List<Team> teams = new ArrayList<>(); for (Participant p : model.getParticipants()) { Team team = (Team) p; teams.add(teamDAO.findByIdFetchWithPlayers(team.getId())); } ranking = getTeamRanking(model.getGender(), teams, LocalDate.now()); } } Collections.sort(ranking); return ranking; } private List<Ranking> getTeamRanking(Gender gender, List<Ranking> rankings, Collection<Team> teams, LocalDate date) { List<Ranking> teamRanking = new ArrayList<>(); teams.forEach(team -> { BigDecimal teamScore = BigDecimal.ZERO; for (Player player : team.getPlayers()) { Optional<Ranking> ranking = rankings.stream().filter(r -> r.getParticipant().equals(player)) .findFirst(); if (ranking.isPresent()) { teamScore = teamScore.add(ranking.get().getValue()); } else { teamScore = teamScore.add(player.getInitialRankingAsBigDecimal()); } } teamScore = teamScore.divide(new BigDecimal(team.getPlayers().size())); teamScore = teamScore.setScale(0, RoundingMode.HALF_UP); teamRanking.removeIf(ranking -> ranking.getParticipant().equals(team)); teamRanking.add(new Ranking(team, gender, teamScore, date)); }); Collections.sort(teamRanking); return teamRanking; } private List<Ranking> getRanking(List<Game> games, Gender gender, LocalDate date) { if (date == null) { date = LocalDate.now(); } List<Ranking> rankings = new ArrayList<>(); Set<Game> sortedGames = new TreeSet<>(new GameByStartDateComparator()); sortedGames.addAll(games); for (Game game : sortedGames) { Set<Participant> participants = game.getParticipants(); if (participants.size() != 2) { LOG.warn("Skipping game " + game + " as it does not have 2 participants"); continue; } if (game.getGameSets().isEmpty()) { LOG.debug("Skipping game " + game + " as no game sets have been played"); continue; } Iterator<Participant> iterator = participants.iterator(); Participant p1 = iterator.next(); Participant p2 = iterator.next(); if (p1 instanceof Team && p2 instanceof Team) { updateRanking(rankings, game, gender, (Team) p1, (Team) p2, date); } else { updateRanking(rankings, game, gender, p1, p2, date); } } Collections.sort(rankings); return rankings; } private void updateRanking(List<Ranking> rankingMap, Game game, Gender gender, Team team1, Team team2, LocalDate date) { BigDecimal r1 = getTeamRanking(rankingMap, team1); BigDecimal r2 = getTeamRanking(rankingMap, team2); for (Player player : team1.getPlayers()) { BigDecimal r1p1 = getRanking(rankingMap, player); BigDecimal tr1 = getTransformedRating(r1); BigDecimal tr2 = getTransformedRating(r2); BigDecimal e1 = getExpectedScore(tr1, tr2); BigDecimal s1 = getScore(game, team1); BigDecimal newR1 = ELO_K_FACTOR.multiply((s1.subtract(e1))).add(r1p1); rankingMap.removeIf(r -> r.getParticipant().getId().equals(player.getId())); rankingMap.add(new Ranking(player, gender, newR1, date)); } for (Player player : team2.getPlayers()) { BigDecimal r2p1 = getRanking(rankingMap, player); BigDecimal tr1 = getTransformedRating(r2); BigDecimal tr2 = getTransformedRating(r1); BigDecimal e1 = getExpectedScore(tr1, tr2); BigDecimal s1 = getScore(game, team2); BigDecimal newR1 = ELO_K_FACTOR.multiply((s1.subtract(e1))).add(r2p1); rankingMap.removeIf(r -> r.getParticipant().getId().equals(player.getId())); rankingMap.add(new Ranking(player, gender, newR1, date)); } } private void updateRanking(List<Ranking> rankingMap, Game game, Gender gender, Participant p1, Participant p2, LocalDate date) { BigDecimal r1 = getRanking(rankingMap, p1); BigDecimal r2 = getRanking(rankingMap, p2); BigDecimal tr1 = getTransformedRating(r1); BigDecimal tr2 = getTransformedRating(r2); BigDecimal e1 = getExpectedScore(tr1, tr2); BigDecimal e2 = getExpectedScore(tr2, tr1); BigDecimal s1 = getScore(game, p1); BigDecimal s2 = getScore(game, p2); BigDecimal newR1 = ELO_K_FACTOR.multiply((s1.subtract(e1))).add(r1); BigDecimal newR2 = ELO_K_FACTOR.multiply((s2.subtract(e2))).add(r2); rankingMap.removeIf(ranking -> ranking.getParticipant().getId().equals(p1.getId())); rankingMap.removeIf(ranking -> ranking.getParticipant().getId().equals(p2.getId())); rankingMap.add(new Ranking(p1, gender, newR1, date)); rankingMap.add(new Ranking(p2, gender, newR2, date)); } private BigDecimal getRanking(List<Ranking> rankings, Participant participant) { Optional<Ranking> ranking = rankings.stream() .filter(r -> r.getParticipant().getId().equals(participant.getId())).findFirst(); if (ranking.isPresent()) { return ranking.get().getValue(); } return participant.getInitialRankingAsBigDecimal(); } private BigDecimal getTransformedRating(BigDecimal rating) { BigDecimal pow = rating.divide(ELO_MAGIC_NUMBER); Double bla = Math.pow(10d, pow.doubleValue()); return new BigDecimal(bla, MathContext.DECIMAL128); } private BigDecimal getExpectedScore(BigDecimal r1, BigDecimal r2) { return r1.divide((r1.add(r2)), MathContext.DECIMAL128); } private BigDecimal getScore(Game game, Participant participant) { Map<Integer, Integer> setMapT1 = new HashMap<>(); Map<Integer, Integer> setMapT2 = new HashMap<>(); for (GameSet gameSet : game.getGameSets()) { Participant p1 = gameSet.getParticipant(); if (p1.equals(participant)) { setMapT1.put(gameSet.getSetNumber(), gameSet.getSetGames()); } else { setMapT2.put(gameSet.getSetNumber(), gameSet.getSetGames()); } } int setsPlayed = game.getGameSets().size() / 2; int setsWon = 0; for (Map.Entry<Integer, Integer> entry : setMapT1.entrySet()) { Integer set = entry.getKey(); Integer games = entry.getValue(); int gamesWonP1 = games == null ? 0 : games; int gamesWonP2 = setMapT2.get(set) == null ? 0 : setMapT2.get(set); if (gamesWonP1 > gamesWonP2) { setsWon++; } } if (setsWon > (setsPlayed - setsWon)) { return new BigDecimal(1); } else if (setsWon < (setsPlayed - setsWon)) { return new BigDecimal(0); } return new BigDecimal(0.5); } private BigDecimal getTeamRanking(List<Ranking> rankings, Team team) { BigDecimal ranking = BigDecimal.ZERO; for (Player player : team.getPlayers()) { ranking = ranking.add(getRanking(rankings, player)); } ranking = ranking.divide(new BigDecimal(team.getPlayers().size())); return ranking; } public ScoreEntry getScore(Participant participant, Collection<Game> games) { int matchesPlayed = 0; int matchesWon = 0; int totalSetsPlayed = 0; int totalSetsWon = 0; int totalGamesPlayed = 0; int totalGamesWon = 0; List<GameSet> gameSets = new ArrayList<>(); for (Game game : games) { gameSets.addAll(game.getGameSets()); } for (Game game : games) { if (game.getParticipants().contains(participant)) { int setsPlayed = 0; int setsWon = 0; Map<Integer, Integer> setMapP1 = getSetMapForParticipant(game, participant, gameSets); Map<Integer, Integer> setMapP2 = new HashMap<>(); for (Participant opponent : game.getParticipants()) { if (!opponent.equals(participant)) { setMapP2 = getSetMapForParticipant(game, opponent, gameSets); break; } } for (Map.Entry<Integer, Integer> entry : setMapP1.entrySet()) { Integer set = entry.getKey(); Integer setGames = entry.getValue(); setsPlayed++; totalSetsPlayed++; int gamesWonP1 = setGames == null ? 0 : setGames; int gamesWonP2 = setMapP2.get(set) == null ? 0 : setMapP2.get(set); totalGamesWon += gamesWonP1; setsWon += gamesWonP1 > gamesWonP2 ? 1 : 0; totalSetsWon += gamesWonP1 > gamesWonP2 ? 1 : 0; totalGamesPlayed += gamesWonP1 + gamesWonP2; } if (setsPlayed > 0) { matchesPlayed++; matchesWon += setsWon > (setsPlayed - setsWon) ? 1 : 0; } } } ScoreEntry entry = new ScoreEntry(); entry.setParticipant(participant); entry.setMatchesPlayed(matchesPlayed); entry.setMatchesWon(matchesWon); entry.setSetsPlayed(totalSetsPlayed); entry.setSetsWon(totalSetsWon); entry.setGamesWon(totalGamesWon); entry.setGamesPlayed(totalGamesPlayed); return entry; } public List<ScoreEntry> getScores(Collection<Participant> participants, Collection<Game> eventGames) { List<ScoreEntry> scoreEntries = new ArrayList<>(); for (Participant p : participants) { scoreEntries.add(getScore(p, eventGames)); } Collections.sort(scoreEntries); return scoreEntries; } private Map<Integer, Integer> getSetMapForParticipant(Game game, Participant participant, Collection<GameSet> gameSets) { Map<Integer, Integer> setMap = new HashMap<>(); for (GameSet set : gameSets) { if (set.getGame().equals(game) && set.getParticipant().equals(participant)) { setMap.put(set.getSetNumber(), set.getSetGames()); } } return setMap; } private List<Game> getGamesInLastYearSince(Gender gender, LocalDate date) { LocalDate since = date.minusDays(ELO_MAX_DAYS); List<Game> games = gameDAO.findAllYoungerThanForGenderWithPlayers(since, gender); return games; } private List<Game> getGamesInLastYearSince(Gender gender, LocalDate date, Customer customer) { LocalDate since = date.minusDays(ELO_MAX_DAYS); List<Game> games = gameBaseDAO.findAllYoungerThanForGenderWithPlayers(since, gender, customer); return games; } }