From c0238468aae4e8c26e0fb1b0d5bdea2c9aa000ae Mon Sep 17 00:00:00 2001 From: Scott Yargeau Date: Mon, 2 Jul 2018 22:25:24 -0400 Subject: [PATCH] Increase ranking efficiency --- leaderboard/models.py | 26 +++++++++++++++++++------- leaderboard/rankings.py | 8 +++++++- leaderboard/tests/test_models.py | 1 + leaderboard/tests/test_rankings.py | 9 +++++++++ 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/leaderboard/models.py b/leaderboard/models.py index f93e84d..749a361 100644 --- a/leaderboard/models.py +++ b/leaderboard/models.py @@ -57,31 +57,43 @@ def date(self): @property def description(self): + """Description of who defeated who and what the score was.""" description = ( f'{self.date}: {self.winner} defeated {self.loser} {self.score}' ) return description def save(self, *args, **kwargs): - super().save(*args, **kwargs) - PlayerRating.generate_ratings() + if self.id: # occurs when the match already exists and is being updated + super().save(*args, **kwargs) + PlayerRating.generate_ratings() + else: # occurs when it's a new match being added + super().save(*args, **kwargs) + elo_rating = EloRating(use_current_ratings=True) + elo_rating.update_ratings(self.winner, self.loser) + PlayerRating.add_ratings(elo_rating) class PlayerRating(models.Model): """Table for keeping track of a player's rating.""" player = models.OneToOneField(Player, default=None, primary_key=True) rating = models.IntegerField(default=None, blank=False) + + @staticmethod + def add_ratings(elo_rating: EloRating): + """Add ratings to database given EloRating object.""" + PlayerRating.objects.all().delete() + for player, rating in elo_rating.ratings.items(): + PlayerRating.objects.create(player=player, rating=rating) @staticmethod def generate_ratings(): - """Generate ratings from all previous matches.""" - PlayerRating.objects.all().delete() - matches = Match.objects.all().order_by('datetime') + """Generate ratings from scratch based on all previous matches.""" elo_rating = EloRating() + matches = Match.objects.all().order_by('datetime') for match in matches: elo_rating.update_ratings(match.winner, match.loser) - for player, rating in elo_rating.ratings.items(): - PlayerRating.objects.create(player=player, rating=rating) + PlayerRating.add_ratings(elo_rating) @property def games_played(self): diff --git a/leaderboard/rankings.py b/leaderboard/rankings.py index ad46a89..f36d2dc 100644 --- a/leaderboard/rankings.py +++ b/leaderboard/rankings.py @@ -1,3 +1,5 @@ +import leaderboard.models + DEFAULT_ELO_RATING = 1000 DEFAULT_K_FACTOR = 30 @@ -5,8 +7,12 @@ class EloRating(object): """Uses Elo rating system to rate players.""" - def __init__(self): + def __init__(self, use_current_ratings=False): self.ratings = {} + if use_current_ratings: + rated_players = leaderboard.models.PlayerRating.objects.all() + for rated_player in rated_players: + self.ratings[rated_player.player] = rated_player.rating def get_rating(self, player): """Return the rating of the specified player.""" diff --git a/leaderboard/tests/test_models.py b/leaderboard/tests/test_models.py index fe00420..cb25484 100644 --- a/leaderboard/tests/test_models.py +++ b/leaderboard/tests/test_models.py @@ -238,6 +238,7 @@ def test_generate_ratings_in_order(self): losing_score=19, datetime=pytz.utc.localize(datetime(2000, 1, 1)) ) + PlayerRating.generate_ratings() ranking1 = PlayerRating.objects.get(pk=self.player1.id) ranking2 = PlayerRating.objects.get(pk=self.player2.id) self.assertGreater(ranking2.rating, ranking1.rating) diff --git a/leaderboard/tests/test_rankings.py b/leaderboard/tests/test_rankings.py index 072a605..5eeacbc 100644 --- a/leaderboard/tests/test_rankings.py +++ b/leaderboard/tests/test_rankings.py @@ -1,6 +1,7 @@ from django.test import TestCase from leaderboard.rankings import EloRating, DEFAULT_ELO_RATING, DEFAULT_K_FACTOR +from leaderboard.models import PlayerRating, Player class EloRatingTest(TestCase): @@ -69,3 +70,11 @@ def test_rating_updates(self): expected_loser_rating = DEFAULT_ELO_RATING - DEFAULT_K_FACTOR * 0.5 self.assertEqual(winner_rating, expected_winner_rating) self.assertEqual(loser_rating, expected_loser_rating) + + def test_use_current_rating(self): + """Test that the EloRating class can use current ratings.""" + player = Player.objects.create(first_name='Bob', last_name='Hope') + test_rating = 1013 + rated_player = PlayerRating.objects.create(player=player, rating=test_rating) + rating = EloRating(use_current_ratings=True) + self.assertEqual(test_rating, rating.get_rating(player))