Python: tournament

This commit is contained in:
Dmitry Kokorin 2021-06-16 16:34:51 +03:00
parent bae42c9a49
commit 2ef852f257
4 changed files with 302 additions and 0 deletions

View file

@ -0,0 +1 @@
{"track":"python","exercise":"tournament","id":"9d29ba55dcb24a2599bd0feab54e2300","url":"https://exercism.io/my/solutions/9d29ba55dcb24a2599bd0feab54e2300","handle":"DmitryKokorin","is_requester":true,"auto_approve":false}

108
python/tournament/README.md Normal file
View file

@ -0,0 +1,108 @@
# Tournament
Tally the results of a small football competition.
Based on an input file containing which team played against which and what the
outcome was, create a file with a table like this:
```text
Team | MP | W | D | L | P
Devastating Donkeys | 3 | 2 | 1 | 0 | 7
Allegoric Alaskans | 3 | 2 | 0 | 1 | 6
Blithering Badgers | 3 | 1 | 0 | 2 | 3
Courageous Californians | 3 | 0 | 1 | 2 | 1
```
What do those abbreviations mean?
- MP: Matches Played
- W: Matches Won
- D: Matches Drawn (Tied)
- L: Matches Lost
- P: Points
A win earns a team 3 points. A draw earns 1. A loss earns 0.
The outcome should be ordered by points, descending. In case of a tie, teams are ordered alphabetically.
###
Input
Your tallying program will receive input that looks like:
```text
Allegoric Alaskans;Blithering Badgers;win
Devastating Donkeys;Courageous Californians;draw
Devastating Donkeys;Allegoric Alaskans;win
Courageous Californians;Blithering Badgers;loss
Blithering Badgers;Devastating Donkeys;loss
Allegoric Alaskans;Courageous Californians;win
```
The result of the match refers to the first team listed. So this line
```text
Allegoric Alaskans;Blithering Badgers;win
```
Means that the Allegoric Alaskans beat the Blithering Badgers.
This line:
```text
Courageous Californians;Blithering Badgers;loss
```
Means that the Blithering Badgers beat the Courageous Californians.
And this line:
```text
Devastating Donkeys;Courageous Californians;draw
```
Means that the Devastating Donkeys and Courageous Californians tied.
## Exception messages
Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to
indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not
every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include
a message.
To raise a message with an exception, just write it as an argument to the exception type. For example, instead of
`raise Exception`, you should write:
```python
raise Exception("Meaningful message indicating the source of the error")
```
## Running the tests
To run the tests, run `pytest tournament_test.py`
Alternatively, you can tell Python to run the pytest module:
`python -m pytest tournament_test.py`
### Common `pytest` options
- `-v` : enable verbose output
- `-x` : stop running tests on first failure
- `--ff` : run failures from previous test before running other test cases
For other options, see `python -m pytest -h`
## Submitting Exercises
Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/tournament` directory.
You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`.
For more detailed information about running tests, code style and linting,
please see [Running the Tests](http://exercism.io/tracks/python/tests).
## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

View file

@ -0,0 +1,51 @@
from collections import defaultdict
SCORES = {'win': 3, 'draw': 1, 'loss': 0}
GUEST_RESULT = {'win': 'loss', 'draw': 'draw', 'loss': 'win'}
FORMAT = '{0:<30} | {1:>2} | {2:>2} | {3:>2} | {4:>2} | {5:>2}'
class TeamStats:
def __init__(self):
self.win = 0
self.draw = 0
self.loss = 0
self.__points = None
def add_game(self, result):
setattr(self, result, getattr(self, result) + 1)
self.__points = None
def played(self):
return sum(getattr(self, x) for x in SCORES.keys())
def points(self):
if self.__points is None:
self.__points = sum(SCORES[x]*getattr(self, x)
for x in SCORES.keys())
return self.__points
def tally(rows):
table = [FORMAT.format('Team', 'MP', 'W', 'D', 'L', 'P')]
stats = defaultdict(TeamStats)
for team_stats in rows:
home_team, guest_team, result = team_stats.split(';')
if result not in SCORES.keys():
raise ValueError("Invalid game result")
stats[home_team].add_game(result)
stats[guest_team].add_game(GUEST_RESULT[result])
for team, team_stats in sorted(stats.items(),
key=lambda x: (-x[1].points(), x[0])):
table.append(FORMAT.format(team, team_stats.played(), team_stats.win,
team_stats.draw, team_stats.loss,
team_stats.points()))
return table

View file

@ -0,0 +1,142 @@
import unittest
from tournament import tally
# Tests adapted from `problem-specifications//canonical-data.json`
class TournamentTest(unittest.TestCase):
def test_just_the_header_if_no_input(self):
results = []
table = ["Team | MP | W | D | L | P"]
self.assertEqual(tally(results), table)
def test_a_win_is_three_points_a_loss_is_zero_points(self):
results = ["Allegoric Alaskans;Blithering Badgers;win"]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 1 | 1 | 0 | 0 | 3",
"Blithering Badgers | 1 | 0 | 0 | 1 | 0",
]
self.assertEqual(tally(results), table)
def test_a_win_can_also_be_expressed_as_a_loss(self):
results = ["Blithering Badgers;Allegoric Alaskans;loss"]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 1 | 1 | 0 | 0 | 3",
"Blithering Badgers | 1 | 0 | 0 | 1 | 0",
]
self.assertEqual(tally(results), table)
def test_a_different_team_can_win(self):
results = ["Blithering Badgers;Allegoric Alaskans;win"]
table = [
"Team | MP | W | D | L | P",
"Blithering Badgers | 1 | 1 | 0 | 0 | 3",
"Allegoric Alaskans | 1 | 0 | 0 | 1 | 0",
]
self.assertEqual(tally(results), table)
def test_a_draw_is_one_point_each(self):
results = ["Allegoric Alaskans;Blithering Badgers;draw"]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 1 | 0 | 1 | 0 | 1",
"Blithering Badgers | 1 | 0 | 1 | 0 | 1",
]
self.assertEqual(tally(results), table)
def test_there_can_be_more_than_one_match(self):
results = [
"Allegoric Alaskans;Blithering Badgers;win",
"Allegoric Alaskans;Blithering Badgers;win",
]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 2 | 2 | 0 | 0 | 6",
"Blithering Badgers | 2 | 0 | 0 | 2 | 0",
]
self.assertEqual(tally(results), table)
def test_there_can_be_more_than_one_winner(self):
results = [
"Allegoric Alaskans;Blithering Badgers;loss",
"Allegoric Alaskans;Blithering Badgers;win",
]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 2 | 1 | 0 | 1 | 3",
"Blithering Badgers | 2 | 1 | 0 | 1 | 3",
]
self.assertEqual(tally(results), table)
def test_there_can_be_more_than_two_teams(self):
results = [
"Allegoric Alaskans;Blithering Badgers;win",
"Blithering Badgers;Courageous Californians;win",
"Courageous Californians;Allegoric Alaskans;loss",
]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 2 | 2 | 0 | 0 | 6",
"Blithering Badgers | 2 | 1 | 0 | 1 | 3",
"Courageous Californians | 2 | 0 | 0 | 2 | 0",
]
self.assertEqual(tally(results), table)
def test_typical_input(self):
results = [
"Allegoric Alaskans;Blithering Badgers;win",
"Devastating Donkeys;Courageous Californians;draw",
"Devastating Donkeys;Allegoric Alaskans;win",
"Courageous Californians;Blithering Badgers;loss",
"Blithering Badgers;Devastating Donkeys;loss",
"Allegoric Alaskans;Courageous Californians;win",
]
table = [
"Team | MP | W | D | L | P",
"Devastating Donkeys | 3 | 2 | 1 | 0 | 7",
"Allegoric Alaskans | 3 | 2 | 0 | 1 | 6",
"Blithering Badgers | 3 | 1 | 0 | 2 | 3",
"Courageous Californians | 3 | 0 | 1 | 2 | 1",
]
self.assertEqual(tally(results), table)
def test_incomplete_competition_not_all_pairs_have_played(self):
results = [
"Allegoric Alaskans;Blithering Badgers;loss",
"Devastating Donkeys;Allegoric Alaskans;loss",
"Courageous Californians;Blithering Badgers;draw",
"Allegoric Alaskans;Courageous Californians;win",
]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 3 | 2 | 0 | 1 | 6",
"Blithering Badgers | 2 | 1 | 1 | 0 | 4",
"Courageous Californians | 2 | 0 | 1 | 1 | 1",
"Devastating Donkeys | 1 | 0 | 0 | 1 | 0",
]
self.assertEqual(tally(results), table)
def test_ties_broken_alphabetically(self):
results = [
"Courageous Californians;Devastating Donkeys;win",
"Allegoric Alaskans;Blithering Badgers;win",
"Devastating Donkeys;Allegoric Alaskans;loss",
"Courageous Californians;Blithering Badgers;win",
"Blithering Badgers;Devastating Donkeys;draw",
"Allegoric Alaskans;Courageous Californians;draw",
]
table = [
"Team | MP | W | D | L | P",
"Allegoric Alaskans | 3 | 2 | 1 | 0 | 7",
"Courageous Californians | 3 | 2 | 1 | 0 | 7",
"Blithering Badgers | 3 | 0 | 1 | 2 | 1",
"Devastating Donkeys | 3 | 0 | 1 | 2 | 1",
]
self.assertEqual(tally(results), table)
if __name__ == "__main__":
unittest.main()