Implement Elo Rating System
Problem
In chess, the Elo rating system is used to calculate player strengths based on game results.
A simplified description of the Elo system is as follows. Every player begins at the same score. For each subsequent game, the loser transfers some points to the winner, where the amount of points transferred depends on how unlikely the win is. For example, a 1200-ranked player should gain much more points for beating a 2000-ranked player than for beating a 1300-ranked player.
Implement this system.
Examples
Example 1
Input:
- Player A rating: 1200
- Player B rating: 1300
- Result: A wins
- K-factor: 32
Output:
- Player A new rating: 1219
- Player B new rating: 1281
Explanation:
Expected score for A = 1/(1+10^((1300-1200)/400)) = 0.36
Rating change = 32 * (1 - 0.36) = 20.48 ≈ 19
A: 1200 + 19 = 1219, B: 1300 - 19 = 1281
Example 2
Input:
- Player A rating: 1200
- Player B rating: 2000
- Result: A wins
- K-factor: 32
Output:
- Player A new rating: 1231
- Player B new rating: 1969
Explanation:
Expected score for A = 1/(1+10^((2000-1200)/400)) = 0.009
Rating change = 32 * (1 - 0.009) = 31.71 ≈ 31
A: 1200 + 31 = 1231, B: 2000 - 31 = 1969
Example 3
Input:
- Player A rating: 2000
- Player B rating: 1200
- Result: A wins
- K-factor: 32
Output:
- Player A new rating: 2000
- Player B new rating: 1200
Explanation:
Expected score for A = 1/(1+10^((1200-2000)/400)) = 0.991
Rating change = 32 * (1 - 0.991) = 0.29 ≈ 0
A: 2000 + 0 = 2000, B: 1200 - 0 = 1200
Example 4
Input:
- Player A rating: 1500
- Player B rating: 1500
- Result: Draw
- K-factor: 32
Output:
- Player A new rating: 1500
- Player B new rating: 1500
Explanation:
Expected score for both = 0.5 (equal ratings)
For draw, actual score = 0.5 for both
Rating change = 32 * (0.5 - 0.5) = 0
Both players remain at 1500
Example 5
Input:
- Player A rating: 1800
- Player B rating: 1600
- Result: B wins
- K-factor: 16
Output:
- Player A new rating: 1788
- Player B new rating: 1612
Explanation:
Expected score for A = 1/(1+10^((1600-1800)/400)) = 0.76
Rating change = 16 * (0 - 0.76) = -12.16 ≈ -12
A: 1800 - 12 = 1788, B: 1600 + 12 = 1612
Solution
Method 1 - Standard Elo Rating Calculation
Intuition
The Elo rating system calculates expected performance based on rating differences and adjusts ratings based on actual vs expected results. The core formula uses the logistic curve to determine win probability, then applies a K-factor to scale the rating change. Larger rating differences lead to more extreme expected scores, making upsets worth more points.
Approach
- Calculate expected score for each player using the Elo formula:
E = 1 / (1 + 10^((opponent_rating - player_rating) / 400)) - Determine actual scores based on game result (1 for win, 0 for loss, 0.5 for draw)
- Calculate rating changes using:
new_rating = old_rating + K * (actual_score - expected_score) - Apply changes to both players (one gains what the other loses)
- Round to nearest integer for final ratings
- Handle edge cases like draws and equal ratings
Code
C++
class EloRatingSystem {
private:
double kFactor;
public:
EloRatingSystem(double k = 32.0) : kFactor(k) {}
pair<int, int> updateRatings(int ratingA, int ratingB, int result) {
// Calculate expected scores
double expectedA = calculateExpectedScore(ratingA, ratingB);
double expectedB = 1.0 - expectedA;
// Determine actual scores based on result
double actualA, actualB;
if (result == 1) { // A wins
actualA = 1.0;
actualB = 0.0;
} else if (result == -1) { // B wins
actualA = 0.0;
actualB = 1.0;
} else { // Draw
actualA = 0.5;
actualB = 0.5;
}
// Calculate rating changes
int changeA = round(kFactor * (actualA - expectedA));
int changeB = round(kFactor * (actualB - expectedB));
return {ratingA + changeA, ratingB + changeB};
}
private:
double calculateExpectedScore(int rating, int opponentRating) {
double diff = (double)(opponentRating - rating) / 400.0;
return 1.0 / (1.0 + pow(10.0, diff));
}
};
class Solution {
public:
pair<int, int> updateEloRatings(int ratingA, int ratingB, int result, int k = 32) {
EloRatingSystem elo(k);
return elo.updateRatings(ratingA, ratingB, result);
}
};
Go
import (
"math"
)
type EloRatingSystem struct {
kFactor float64
}
func NewEloRatingSystem(k float64) *EloRatingSystem {
return &EloRatingSystem{kFactor: k}
}
func (e *EloRatingSystem) UpdateRatings(ratingA, ratingB, result int) (int, int) {
// Calculate expected scores
expectedA := e.calculateExpectedScore(ratingA, ratingB)
expectedB := 1.0 - expectedA
// Determine actual scores based on result
var actualA, actualB float64
switch result {
case 1: // A wins
actualA, actualB = 1.0, 0.0
case -1: // B wins
actualA, actualB = 0.0, 1.0
default: // Draw
actualA, actualB = 0.5, 0.5
}
// Calculate rating changes
changeA := int(math.Round(e.kFactor * (actualA - expectedA)))
changeB := int(math.Round(e.kFactor * (actualB - expectedB)))
return ratingA + changeA, ratingB + changeB
}
func (e *EloRatingSystem) calculateExpectedScore(rating, opponentRating int) float64 {
diff := float64(opponentRating-rating) / 400.0
return 1.0 / (1.0 + math.Pow(10.0, diff))
}
func updateEloRatings(ratingA, ratingB, result, k int) (int, int) {
elo := NewEloRatingSystem(float64(k))
return elo.UpdateRatings(ratingA, ratingB, result)
}
Java
class EloRatingSystem {
private double kFactor;
public EloRatingSystem(double k) {
this.kFactor = k;
}
public int[] updateRatings(int ratingA, int ratingB, int result) {
// Calculate expected scores
double expectedA = calculateExpectedScore(ratingA, ratingB);
double expectedB = 1.0 - expectedA;
// Determine actual scores based on result
double actualA, actualB;
if (result == 1) { // A wins
actualA = 1.0;
actualB = 0.0;
} else if (result == -1) { // B wins
actualA = 0.0;
actualB = 1.0;
} else { // Draw
actualA = 0.5;
actualB = 0.5;
}
// Calculate rating changes
int changeA = (int) Math.round(kFactor * (actualA - expectedA));
int changeB = (int) Math.round(kFactor * (actualB - expectedB));
return new int[]{ratingA + changeA, ratingB + changeB};
}
private double calculateExpectedScore(int rating, int opponentRating) {
double diff = (double)(opponentRating - rating) / 400.0;
return 1.0 / (1.0 + Math.pow(10.0, diff));
}
}
class Solution {
public int[] updateEloRatings(int ratingA, int ratingB, int result, int k) {
EloRatingSystem elo = new EloRatingSystem(k);
return elo.updateRatings(ratingA, ratingB, result);
}
}
Python
import math
from typing import Tuple
class EloRatingSystem:
def __init__(self, k_factor: float = 32.0):
self.k_factor = k_factor
def update_ratings(self, rating_a: int, rating_b: int, result: int) -> Tuple[int, int]:
# Calculate expected scores
expected_a = self._calculate_expected_score(rating_a, rating_b)
expected_b = 1.0 - expected_a
# Determine actual scores based on result
if result == 1: # A wins
actual_a, actual_b = 1.0, 0.0
elif result == -1: # B wins
actual_a, actual_b = 0.0, 1.0
else: # Draw
actual_a, actual_b = 0.5, 0.5
# Calculate rating changes
change_a = round(self.k_factor * (actual_a - expected_a))
change_b = round(self.k_factor * (actual_b - expected_b))
return rating_a + change_a, rating_b + change_b
def _calculate_expected_score(self, rating: int, opponent_rating: int) -> float:
diff = (opponent_rating - rating) / 400.0
return 1.0 / (1.0 + math.pow(10.0, diff))
class Solution:
def update_elo_ratings(self, rating_a: int, rating_b: int, result: int, k: int = 32) -> Tuple[int, int]:
elo = EloRatingSystem(k)
return elo.update_ratings(rating_a, rating_b, result)
Complexity
- ⏰ Time complexity:
O(1), all calculations are constant time mathematical operations - 🧺 Space complexity:
O(1), only using constant extra space for calculations
Method 2 - Tournament Management System
Intuition
For managing multiple players and games, we need a more comprehensive system that tracks player histories, handles different K-factors based on player experience, and maintains a leaderboard. This approach extends the basic Elo calculation to a full tournament management system.
Approach
- Create a Player class to store rating, games played, and history
- Implement different K-factors based on player experience (higher for new players)
- Maintain a tournament system that can process multiple games
- Add methods for ranking players and calculating statistics
- Handle edge cases like minimum/maximum ratings and provisional ratings
- Provide methods to query player performance and rating trends
Code
C++
class Player {
public:
string name;
int rating;
int gamesPlayed;
vector<int> ratingHistory;
Player(string n, int initialRating = 1500)
: name(n), rating(initialRating), gamesPlayed(0) {
ratingHistory.push_back(initialRating);
}
int getKFactor() {
if (gamesPlayed < 30) return 40; // New players
else if (rating < 2100) return 20; // Regular players
else return 10; // Expert players
}
void updateRating(int newRating) {
rating = max(100, min(3000, newRating)); // Clamp between 100-3000
gamesPlayed++;
ratingHistory.push_back(rating);
}
};
class TournamentEloSystem {
private:
unordered_map<string, Player> players;
public:
void addPlayer(string name, int initialRating = 1500) {
players[name] = Player(name, initialRating);
}
void recordGame(string playerA, string playerB, int result) {
if (players.find(playerA) == players.end() ||
players.find(playerB) == players.end()) {
return; // Player not found
}
Player& pA = players[playerA];
Player& pB = players[playerB];
// Use average K-factor for the game
double kFactor = (pA.getKFactor() + pB.getKFactor()) / 2.0;
// Calculate expected scores
double expectedA = 1.0 / (1.0 + pow(10.0, (pB.rating - pA.rating) / 400.0));
double expectedB = 1.0 - expectedA;
// Determine actual scores
double actualA, actualB;
if (result == 1) {
actualA = 1.0; actualB = 0.0;
} else if (result == -1) {
actualA = 0.0; actualB = 1.0;
} else {
actualA = 0.5; actualB = 0.5;
}
// Calculate and apply rating changes
int changeA = round(kFactor * (actualA - expectedA));
int changeB = round(kFactor * (actualB - expectedB));
pA.updateRating(pA.rating + changeA);
pB.updateRating(pB.rating + changeB);
}
vector<pair<string, int>> getLeaderboard() {
vector<pair<string, int>> leaderboard;
for (auto& p : players) {
leaderboard.push_back({p.second.name, p.second.rating});
}
sort(leaderboard.begin(), leaderboard.end(),
[](const auto& a, const auto& b) { return a.second > b.second; });
return leaderboard;
}
int getPlayerRating(string name) {
return players.find(name) != players.end() ? players[name].rating : -1;
}
};
class Solution {
public:
pair<int, int> processGameAdvanced(string playerA, string playerB,
int ratingA, int ratingB, int result) {
TournamentEloSystem tournament;
tournament.addPlayer(playerA, ratingA);
tournament.addPlayer(playerB, ratingB);
tournament.recordGame(playerA, playerB, result);
return {tournament.getPlayerRating(playerA),
tournament.getPlayerRating(playerB)};
}
};
Go
type Player struct {
Name string
Rating int
GamesPlayed int
RatingHistory []int
}
func NewPlayer(name string, initialRating int) *Player {
return &Player{
Name: name,
Rating: initialRating,
GamesPlayed: 0,
RatingHistory: []int{initialRating},
}
}
func (p *Player) GetKFactor() int {
if p.GamesPlayed < 30 {
return 40 // New players
} else if p.Rating < 2100 {
return 20 // Regular players
}
return 10 // Expert players
}
func (p *Player) UpdateRating(newRating int) {
if newRating < 100 {
newRating = 100
}
if newRating > 3000 {
newRating = 3000
}
p.Rating = newRating
p.GamesPlayed++
p.RatingHistory = append(p.RatingHistory, newRating)
}
type TournamentEloSystem struct {
Players map[string]*Player
}
func NewTournamentEloSystem() *TournamentEloSystem {
return &TournamentEloSystem{
Players: make(map[string]*Player),
}
}
func (t *TournamentEloSystem) AddPlayer(name string, initialRating int) {
t.Players[name] = NewPlayer(name, initialRating)
}
func (t *TournamentEloSystem) RecordGame(playerA, playerB string, result int) {
pA, okA := t.Players[playerA]
pB, okB := t.Players[playerB]
if !okA || !okB {
return // Player not found
}
// Use average K-factor for the game
kFactor := float64(pA.GetKFactor()+pB.GetKFactor()) / 2.0
// Calculate expected scores
diff := float64(pB.Rating-pA.Rating) / 400.0
expectedA := 1.0 / (1.0 + math.Pow(10.0, diff))
expectedB := 1.0 - expectedA
// Determine actual scores
var actualA, actualB float64
switch result {
case 1:
actualA, actualB = 1.0, 0.0
case -1:
actualA, actualB = 0.0, 1.0
default:
actualA, actualB = 0.5, 0.5
}
// Calculate and apply rating changes
changeA := int(math.Round(kFactor * (actualA - expectedA)))
changeB := int(math.Round(kFactor * (actualB - expectedB)))
pA.UpdateRating(pA.Rating + changeA)
pB.UpdateRating(pB.Rating + changeB)
}
func (t *TournamentEloSystem) GetPlayerRating(name string) int {
if player, ok := t.Players[name]; ok {
return player.Rating
}
return -1
}
func processGameAdvanced(playerA, playerB string, ratingA, ratingB, result int) (int, int) {
tournament := NewTournamentEloSystem()
tournament.AddPlayer(playerA, ratingA)
tournament.AddPlayer(playerB, ratingB)
tournament.RecordGame(playerA, playerB, result)
return tournament.GetPlayerRating(playerA), tournament.GetPlayerRating(playerB)
}
Java
class Player {
String name;
int rating;
int gamesPlayed;
List<Integer> ratingHistory;
public Player(String name, int initialRating) {
this.name = name;
this.rating = initialRating;
this.gamesPlayed = 0;
this.ratingHistory = new ArrayList<>();
this.ratingHistory.add(initialRating);
}
public int getKFactor() {
if (gamesPlayed < 30) return 40; // New players
else if (rating < 2100) return 20; // Regular players
else return 10; // Expert players
}
public void updateRating(int newRating) {
this.rating = Math.max(100, Math.min(3000, newRating));
this.gamesPlayed++;
this.ratingHistory.add(this.rating);
}
}
class TournamentEloSystem {
private Map<String, Player> players;
public TournamentEloSystem() {
this.players = new HashMap<>();
}
public void addPlayer(String name, int initialRating) {
players.put(name, new Player(name, initialRating));
}
public void recordGame(String playerA, String playerB, int result) {
Player pA = players.get(playerA);
Player pB = players.get(playerB);
if (pA == null || pB == null) return;
// Use average K-factor for the game
double kFactor = (pA.getKFactor() + pB.getKFactor()) / 2.0;
// Calculate expected scores
double expectedA = 1.0 / (1.0 + Math.pow(10.0, (pB.rating - pA.rating) / 400.0));
double expectedB = 1.0 - expectedA;
// Determine actual scores
double actualA, actualB;
if (result == 1) {
actualA = 1.0; actualB = 0.0;
} else if (result == -1) {
actualA = 0.0; actualB = 1.0;
} else {
actualA = 0.5; actualB = 0.5;
}
// Calculate and apply rating changes
int changeA = (int) Math.round(kFactor * (actualA - expectedA));
int changeB = (int) Math.round(kFactor * (actualB - expectedB));
pA.updateRating(pA.rating + changeA);
pB.updateRating(pB.rating + changeB);
}
public int getPlayerRating(String name) {
Player player = players.get(name);
return player != null ? player.rating : -1;
}
}
class Solution {
public int[] processGameAdvanced(String playerA, String playerB,
int ratingA, int ratingB, int result) {
TournamentEloSystem tournament = new TournamentEloSystem();
tournament.addPlayer(playerA, ratingA);
tournament.addPlayer(playerB, ratingB);
tournament.recordGame(playerA, playerB, result);
return new int[]{tournament.getPlayerRating(playerA),
tournament.getPlayerRating(playerB)};
}
}
Python
from typing import Dict, List, Tuple
import math
class Player:
def __init__(self, name: str, initial_rating: int = 1500):
self.name = name
self.rating = initial_rating
self.games_played = 0
self.rating_history = [initial_rating]
def get_k_factor(self) -> int:
if self.games_played < 30:
return 40 # New players
elif self.rating < 2100:
return 20 # Regular players
else:
return 10 # Expert players
def update_rating(self, new_rating: int) -> None:
self.rating = max(100, min(3000, new_rating)) # Clamp between 100-3000
self.games_played += 1
self.rating_history.append(self.rating)
class TournamentEloSystem:
def __init__(self):
self.players: Dict[str, Player] = {}
def add_player(self, name: str, initial_rating: int = 1500) -> None:
self.players[name] = Player(name, initial_rating)
def record_game(self, player_a: str, player_b: str, result: int) -> None:
if player_a not in self.players or player_b not in self.players:
return # Player not found
p_a = self.players[player_a]
p_b = self.players[player_b]
# Use average K-factor for the game
k_factor = (p_a.get_k_factor() + p_b.get_k_factor()) / 2.0
# Calculate expected scores
expected_a = 1.0 / (1.0 + math.pow(10.0, (p_b.rating - p_a.rating) / 400.0))
expected_b = 1.0 - expected_a
# Determine actual scores
if result == 1: # A wins
actual_a, actual_b = 1.0, 0.0
elif result == -1: # B wins
actual_a, actual_b = 0.0, 1.0
else: # Draw
actual_a, actual_b = 0.5, 0.5
# Calculate and apply rating changes
change_a = round(k_factor * (actual_a - expected_a))
change_b = round(k_factor * (actual_b - expected_b))
p_a.update_rating(p_a.rating + change_a)
p_b.update_rating(p_b.rating + change_b)
def get_player_rating(self, name: str) -> int:
return self.players[name].rating if name in self.players else -1
def get_leaderboard(self) -> List[Tuple[str, int]]:
return sorted([(p.name, p.rating) for p in self.players.values()],
key=lambda x: x[1], reverse=True)
class Solution:
def process_game_advanced(self, player_a: str, player_b: str,
rating_a: int, rating_b: int, result: int) -> Tuple[int, int]:
tournament = TournamentEloSystem()
tournament.add_player(player_a, rating_a)
tournament.add_player(player_b, rating_b)
tournament.record_game(player_a, player_b, result)
return tournament.get_player_rating(player_a), tournament.get_player_rating(player_b)
Complexity
- ⏰ Time complexity:
O(1)per game,O(N log N)for leaderboard generation where N is number of players - 🧺 Space complexity:
O(N), where N is the number of players stored in the system
Notes
- The standard Elo formula uses base 10 and a divisor of 400 for the rating difference calculation
- K-factor determines how much ratings change per game - higher for new/lower-rated players
- The system maintains rating conservation (total points gained = total points lost)
- Method 1 provides the core Elo calculation for single games
- Method 2 extends to a full tournament management system with player tracking
- Real chess systems often use different K-factors based on player strength and experience
- The logistic curve ensures that rating differences translate to meaningful win probabilities