# PercyGrunwald's solution

## to Tournament in the Elixir Track

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

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
``````

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.

Formatting the output is easy with `String`'s padding functions. All number columns can be left-padded with spaces to a width of 2 characters, while the team name column can be right-padded with spaces to a width of 30.

## Running tests

Execute the tests with:

``````\$ elixir tournament_test.exs
``````

### Pending tests

In the test suites, all but the first test have been skipped.

Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.

For example:

``````# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
``````

Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the test suite.

``````# ExUnit.configure exclude: :pending, trace: true
``````

If you're stuck on something, it may help to look at some of the available resources out there where answers might be found.

## Submitting Incomplete Solutions

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

### tournament_test.exs

``````if !System.get_env("EXERCISM_TEST_EXAMPLES") do
end

ExUnit.start()
ExUnit.configure(trace: true, exclude: :pending)

defmodule TournamentTest do
use ExUnit.Case

# @tag :pending
test "typical input" do
input = [
"Devastating Donkeys;Courageous Californians;draw",
]

expected =
"""
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
"""
|> String.trim()

assert Tournament.tally(input) == expected
end

@tag :pending
test "incomplete competition (not all pairs have played)" do
input = [
]

expected =
"""
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
"""
|> String.trim()

assert Tournament.tally(input) == expected
end

@tag :pending
test "ties broken alphabetically" do
input = [
"Courageous Californians;Devastating Donkeys;win",
]

expected =
"""
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
"""
|> String.trim()

assert Tournament.tally(input) == expected
end

@tag :pending
test "mostly invalid lines" do
# Invalid input lines in an otherwise-valid game still results in valid
# output.
input = [
"",
"Devastating Donkeys;Courageous Californians;win;5",
]

expected =
"""
Team                           | MP |  W |  D |  L |  P
Devastating Donkeys            |  1 |  1 |  0 |  0 |  3
Blithering Badgers             |  1 |  0 |  0 |  1 |  0
"""
|> String.trim()

assert Tournament.tally(input) == expected
end
end``````
``````defmodule Tournament do
@doc """
Given `input` lines representing two teams and whether the first of them won,
lost, or reached a draw, separated by semicolons, calculate the statistics
for each team's number of games played, won, drawn, lost, and total points
for the season, and return a nicely-formatted string table.

A win earns a team 3 points, a draw earns 1 point, and a loss earns nothing.

Order the outcome by most total points for the season, and settle ties by
listing the teams in alphabetical order.
"""
@spec tally(input :: list(String.t())) :: String.t()
def tally(input) do
lines_to_print =
input
|> calculate_stats()
|> get_lines_to_print()

"""
Team                           | MP |  W |  D |  L |  P
#{lines_to_print}
"""
|> String.trim()
end

defp get_lines_to_print(stats) do
stats
|> Map.to_list()
|> Enum.sort_by(fn {_team, %{points: points}} -> points end, &Kernel.>=/2)
|> Enum.map(fn {team, stats} ->
matches_played = "#{stats.wins + stats.draws + stats.losses}"
team_string = String.pad_trailing(team, 30)

"#{team_string} | #{matches_string} | #{wins_string} | #{draws_string} |" <>
" #{losses_string} | #{points_string}"
end)
|> Enum.join("\n")
end

defp calculate_stats(input_lines) do
Enum.reduce(input_lines, %{}, fn line, acc ->
line
|> String.split(";")
|> case do
[winner, loser, "win"] ->
acc
|> update_win(winner)
|> update_loss(loser)

[team1, team2, "draw"] ->
acc
|> update_draw(team1)
|> update_draw(team2)

[loser, winner, "loss"] ->
acc
|> update_win(winner)
|> update_loss(loser)

_ ->
acc
end
end)
end

defp update_win(acc, winner) do
Map.update(acc, winner, merge_init(%{wins: 1, matches_played: 1, points: 3}), fn
current_stats ->
current_stats
|> Map.update(:wins, 1, fn current_wins -> current_wins + 1 end)
|> Map.update(:matches_played, 1, fn current_matches_played ->
current_matches_played + 1
end)
|> Map.update(:points, 3, fn current_points -> current_points + 3 end)
end)
end

defp update_loss(acc, loser) do
Map.update(acc, loser, merge_init(%{losses: 1, matches_played: 1, points: 0}), fn
current_stats ->
current_stats
|> Map.update(:losses, 1, fn current_losses -> current_losses + 1 end)
|> Map.update(:matches_played, 1, fn current_matches_played ->
current_matches_played + 1
end)
end)
end

defp update_draw(acc, team) do
Map.update(acc, team, merge_init(%{draws: 1, matches_played: 1, points: 1}), fn
current_stats ->
current_stats
|> Map.update(:draws, 1, fn current_draws -> current_draws + 1 end)
|> Map.update(:matches_played, 1, fn current_matches_played ->
current_matches_played + 1
end)
|> Map.update(:points, 1, fn current_points -> current_points + 1 end)
end)
end

defp merge_init(map) do
Map.merge(%{wins: 0, losses: 0, draws: 0, matches_played: 0, points: 0}, map)
end
end``````