I saw this task in the 101 documentation for the Raku programming language and wanted to implement it in Python. The code is quite long, but I tried to dot the i's and cross the t's with regards to method separation, validation, documentation, and type hinting.
Suppose that you host a table tennis tournament. The referees tell you the results of each game in the format Player1 Player2 | 3:2, which means that Player1 won against Player2 by 3 to 2 sets. You need a script that sums up how many matches and sets each player has won to determine the overall winner.
from dataclasses import dataclass
from collections import defaultdict
from typing import Tuple
score_text = """Beth Ana Charlie Dave
Ana Dave | 3:0
Charlie Beth | 3:1
Ana Beth | 2:3
Dave Charlie | 3:0
Ana Charlie | 3:1
Beth Dave | 0:3
"""
@dataclass(order=True)
class ScoreboardEntry():
"""
Represents a player's tournament performance
Attributes:
matches (int): Number of matches won
sets (int): Total sets won
"""
matches: int = 0
sets: int = 0
def __iter__(self):
""" Allow tuple unpacking of the ScoreboardEntry data class"""
yield self.matches
yield self.sets
medals = ["🥇", "🥈", "🥉"]
def parse_match(match_data: str) -> Tuple[str, str, int, int]:
"""
Parse match data into its components
Args:
match_data (str): A string in the format "Player1 Player2 | score1:score2"
Returns:
Tuple[str, str, int, int]: Player1, Player2, score1, score2
"""
try:
players, scores = match_data.split("|")
p1, p2 = players.split()
s1, s2 = map(int, scores.split(":"))
except ValueError:
raise ValueError(f"Invalid match data: {match_data}. Must be \"str str | int:int\"")
return p1, p2, s1, s2
def update_scoreboard(
scoreboard: dict[str, ScoreboardEntry],
player1: str,
player2: str,
sets_player1: int,
sets_player2: int) -> None:
"""
Update the scoreboard based on the results of a match
Args:
scoreboard (dict[str, ScoreboardEntry]): The current tournament scoreboard
player1 (str): Name of the first player
player2 (str): Name of the second player
sets_player1 (int): Sets won by the first player
sets_player2 (int): Sets won by the second player
"""
winner = player1 if sets_player1 > sets_player2 else player2
scoreboard[player1].sets += sets_player1
scoreboard[player2].sets += sets_player2
scoreboard[winner].matches += 1
def print_scoreboard(scoreboard: dict[str, ScoreboardEntry]) -> None:
"""
Print the ranked scoreboard of a tournament
Args:
scoreboard (dict[str, ScoreboardEntry]): The scoreboard to print
"""
sorted_scoreboard = sorted(scoreboard.items(), key=lambda i: i[1], reverse=True)
for index, (player, entry) in enumerate(sorted_scoreboard):
medal = medals[index] if index < len(medals) else ""
matches, sets = entry
print(f"{index + 1}. {medal + ' ' if medal else ''}{player}. Matches won: {matches}, sets won: {sets}")
def determine_winner(scoreboard: dict[str, ScoreboardEntry]) -> str:
"""Determine the overall winner of a tournament based on the scoreboard
Args:
scoreboard (dict[str, ScoreboardEntry]): The current tournament scoreboard
Returns:
str: The name of the tournament winner
"""
return max(scoreboard, key=scoreboard.get)
def main(data: str) -> None:
"""
Process tournament data to determine and announce winner
Args:
data (str): tournament data as a multiline string
"""
scoreboard = defaultdict(ScoreboardEntry)
lines = data.splitlines()
for line in lines[1:]: # Skip first line, we don't need the explicit names
try:
p1, p2, s1, s2 = parse_match(line)
update_scoreboard(scoreboard, p1, p2, s1, s2)
except ValueError as err:
print(err)
print_scoreboard(scoreboard)
print()
tournament_winner = determine_winner(scoreboard)
print(f"{tournament_winner} won the tournament🏆. Congratulations🎉!")
if __name__ == "__main__":
main(score_text)