Exercism v3 launches on Sept 1st 2021. Learn more! ๐Ÿš€๐Ÿš€๐Ÿš€
Avatar of rootulp

rootulp's solution

to Tournament in the Python Track

Published at Jan 27 2019 · 0 comments
Instructions
Test suite
Solution

Note:

This exercise has changed since this solution was written.

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:

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:

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

Allegoric Alaskans;Blithering Badgers;win

Means that the Allegoric Alaskans beat the Blithering Badgers.

This line:

Courageous Californians;Blithering Badgers;loss

Means that the Blithering Badgers beat the Courageous Californians.

And this line:

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:

raise Exception("Meaningful message indicating the source of the error")

Running the tests

To run the tests, run the appropriate command below (why they are different):

  • Python 2.7: py.test tournament_test.py
  • Python 3.4+: pytest tournament_test.py

Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version): 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.

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

tournament_test.py

import unittest

from tournament import tally


# Tests adapted from `problem-specifications//canonical-data.json` @ v1.4.0

class TournamentTest(unittest.TestCase):
    def test_just_the_header_if_no_input(self):
        self.assertEqual(
            tally(''),
            'Team                           | MP |  W |  D |  L |  P'
        )

    def test_a_win_is_three_points_and_a_loss_is_zero_points(self):
        results = 'Allegoric Alaskans;Blithering Badgers;win'
        table = ('Team                           | MP |  W |  D |  L |  P\n'
                 'Allegoric Alaskans             |  1 |  1 |  0 |  0 |  3\n'
                 '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\n'
                 'Allegoric Alaskans             |  1 |  1 |  0 |  0 |  3\n'
                 '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\n'
                 'Blithering Badgers             |  1 |  1 |  0 |  0 |  3\n'
                 '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\n'
                 'Allegoric Alaskans             |  1 |  0 |  1 |  0 |  1\n'
                 '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\n'
                   'Allegoric Alaskans;Blithering Badgers;win')
        table = ('Team                           | MP |  W |  D |  L |  P\n'
                 'Allegoric Alaskans             |  2 |  2 |  0 |  0 |  6\n'
                 '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\n'
                   'Allegoric Alaskans;Blithering Badgers;win')
        table = ('Team                           | MP |  W |  D |  L |  P\n'
                 'Allegoric Alaskans             |  2 |  1 |  0 |  1 |  3\n'
                 '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\n'
                   'Blithering Badgers;Courageous Californians;win\n'
                   'Courageous Californians;Allegoric Alaskans;loss')
        table = ('Team                           | MP |  W |  D |  L |  P\n'
                 'Allegoric Alaskans             |  2 |  2 |  0 |  0 |  6\n'
                 'Blithering Badgers             |  2 |  1 |  0 |  1 |  3\n'
                 'Courageous Californians        |  2 |  0 |  0 |  2 |  0')
        self.assertEqual(tally(results), table)

    def test_typical_input(self):
        results = ('Allegoric Alaskans;Blithering Badgers;win\n'
                   'Devastating Donkeys;Courageous Californians;draw\n'
                   'Devastating Donkeys;Allegoric Alaskans;win\n'
                   'Courageous Californians;Blithering Badgers;loss\n'
                   'Blithering Badgers;Devastating Donkeys;loss\n'
                   'Allegoric Alaskans;Courageous Californians;win')

        table = ('Team                           | MP |  W |  D |  L |  P\n'
                 'Devastating Donkeys            |  3 |  2 |  1 |  0 |  7\n'
                 'Allegoric Alaskans             |  3 |  2 |  0 |  1 |  6\n'
                 'Blithering Badgers             |  3 |  1 |  0 |  2 |  3\n'
                 'Courageous Californians        |  3 |  0 |  1 |  2 |  1')

        self.assertEqual(tally(results), table)

    def test_incomplete_competitionnot_not_all_pairs_have_played(self):
        results = ('Allegoric Alaskans;Blithering Badgers;loss\n'
                   'Devastating Donkeys;Allegoric Alaskans;loss\n'
                   'Courageous Californians;Blithering Badgers;draw\n'
                   'Allegoric Alaskans;Courageous Californians;win')

        table = ('Team                           | MP |  W |  D |  L |  P\n'
                 'Allegoric Alaskans             |  3 |  2 |  0 |  1 |  6\n'
                 'Blithering Badgers             |  2 |  1 |  1 |  0 |  4\n'
                 'Courageous Californians        |  2 |  0 |  1 |  1 |  1\n'
                 'Devastating Donkeys            |  1 |  0 |  0 |  1 |  0')

        self.assertEqual(tally(results), table)

    def test_ties_broken_alphabetically(self):
        results = ('Courageous Californians;Devastating Donkeys;win\n'
                   'Allegoric Alaskans;Blithering Badgers;win\n'
                   'Devastating Donkeys;Allegoric Alaskans;loss\n'
                   'Courageous Californians;Blithering Badgers;win\n'
                   'Blithering Badgers;Devastating Donkeys;draw\n'
                   'Allegoric Alaskans;Courageous Californians;draw')

        table = ('Team                           | MP |  W |  D |  L |  P\n'
                 'Allegoric Alaskans             |  3 |  2 |  1 |  0 |  7\n'
                 'Courageous Californians        |  3 |  2 |  1 |  0 |  7\n'
                 'Blithering Badgers             |  3 |  0 |  1 |  2 |  1\n'
                 'Devastating Donkeys            |  3 |  0 |  1 |  2 |  1')

        self.assertEqual(tally(results), table)


if __name__ == '__main__':
    unittest.main()
class Team:

    POINTS_PER_WIN = 3
    POINTS_PER_DRAW = 1

    def __init__(self, name):
        self.name = name
        self.wins = 0
        self.draws = 0
        self.losses = 0

    @property
    def matches_played(self):
        return self.wins + self.losses + self.draws

    @property
    def points(self):
        return (self.wins * self.POINTS_PER_WIN) + (self.draws * self.POINTS_PER_DRAW)

    def tally_win(self):
        self.wins += 1

    def tally_draw(self):
        self.draws += 1

    def tally_loss(self):
        self.losses += 1

    def __str__(self):
        return '{:<30} | {:^3}| {:^3}| {:^3}| {:^3}| {:>2}'.format(self.name, self.matches_played, self.wins, self.draws, self.losses, self.points)


class Tournament:

    WIN = 'win'
    DRAW = 'draw'
    LOSS = 'loss'
    RESULT_SEPERATOR = ';'
    COLUMN_HEADERS = ['Team', 'MP', 'W', 'D', 'L', 'P']

    def __init__(self, results):
        self._teams = {}
        if results:
            self.parse(results)

    def results_table(self):
        table = [self.table_header()]
        for team in self.sorted_teams():
            table.append(str(team))
        return "\n".join(table)

    def sorted_teams(self):
        alphabetic = sorted(self._teams.values(), key=lambda team: team.name)
        alphabetic_descending_points = sorted(alphabetic, key=lambda team: team.points, reverse=True)
        return alphabetic_descending_points

    def parse(self, results):
        for result in results.split("\n"):
            team_a, team_b, outcome = result.split(self.RESULT_SEPERATOR)
            self.maybe_initialize_teams(team_a, team_b)
            self.tally_outcome(team_a, team_b, outcome)

    def tally_outcome(self, team_a, team_b, outcome):
        if outcome == self.WIN:
            self.tally_win(team_a, team_b)
        if outcome == self.LOSS:
            self.tally_loss(team_a, team_b)
        if outcome == self.DRAW:
            self.tally_draw(team_a, team_b)

    def tally_win(self, winner, loser):
        self._teams[winner].tally_win()
        self._teams[loser].tally_loss()

    def tally_loss(self, loser, winner):
        self._teams[loser].tally_loss()
        self._teams[winner].tally_win()

    def tally_draw(self, team_a, team_b):
        self._teams[team_a].tally_draw()
        self._teams[team_b].tally_draw()

    def maybe_initialize_teams(self, team_a, team_b):
        self.maybe_initialize_team(team_a)
        self.maybe_initialize_team(team_b)

    def maybe_initialize_team(self, name):
        if name not in self._teams:
            self._teams[name] = Team(name)

    def table_header(self):
        return '{:<30} | {:^3}| {:^3}| {:^3}| {:^3}| {:>2}'.format(*self.COLUMN_HEADERS)

def tally(results):
    return Tournament(results).results_table()

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other peopleโ€™s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?