ðŸŽ‰ Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io ðŸŽ‰

# lujanfernaud's solution

## to Tournament in the Ruby Track

Published at Jul 13 2020 · 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

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

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.

For installation and learning resources, refer to the Ruby resources page.

For running the tests provided, you will need the Minitest gem. Open a terminal window and run the following command to install minitest:

``````gem install minitest
``````

If you would like color output, you can `require 'minitest/pride'` in the test file, or note the alternative instruction, below, for running the test file.

Run the tests from the exercise directory using the following command:

``````ruby tournament_test.rb
``````

To include color from the command line:

``````ruby -r minitest/pride tournament_test.rb
``````

## Submitting Incomplete Solutions

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

### tournament_test.rb

``````require 'minitest/autorun'
require_relative 'tournament'

# Common test data version: 1.4.0 ee01fe0
class TournamentTest < Minitest::Test
# skip
input = <<~INPUT

INPUT

expected = <<~TALLY
Team                           | MP |  W |  D |  L |  P
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_a_win_is_three_points_a_loss_is_zero_points
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
Team                           | MP |  W |  D |  L |  P
Allegoric Alaskans             |  1 |  1 |  0 |  0 |  3
Blithering Badgers             |  1 |  0 |  0 |  1 |  0
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_a_win_can_also_be_expressed_as_a_loss
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
Team                           | MP |  W |  D |  L |  P
Allegoric Alaskans             |  1 |  1 |  0 |  0 |  3
Blithering Badgers             |  1 |  0 |  0 |  1 |  0
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_a_different_team_can_win
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
Team                           | MP |  W |  D |  L |  P
Blithering Badgers             |  1 |  1 |  0 |  0 |  3
Allegoric Alaskans             |  1 |  0 |  0 |  1 |  0
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_a_draw_is_one_point_each
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
Team                           | MP |  W |  D |  L |  P
Allegoric Alaskans             |  1 |  0 |  1 |  0 |  1
Blithering Badgers             |  1 |  0 |  1 |  0 |  1
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_there_can_be_more_than_one_match
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
Team                           | MP |  W |  D |  L |  P
Allegoric Alaskans             |  2 |  2 |  0 |  0 |  6
Blithering Badgers             |  2 |  0 |  0 |  2 |  0
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_there_can_be_more_than_one_winner
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
Team                           | MP |  W |  D |  L |  P
Allegoric Alaskans             |  2 |  1 |  0 |  1 |  3
Blithering Badgers             |  2 |  1 |  0 |  1 |  3
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_there_can_be_more_than_two_teams
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
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
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_typical_input
skip
input = <<~INPUT
Devastating Donkeys;Courageous Californians;draw
INPUT

expected = <<~TALLY
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
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_incomplete_competition_not_all_pairs_have_played
skip
input = <<~INPUT
INPUT

expected = <<~TALLY
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
TALLY

assert_equal expected, Tournament.tally(input)
end

def test_ties_broken_alphabetically
skip
input = <<~INPUT
Courageous Californians;Devastating Donkeys;win
INPUT

expected = <<~TALLY
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
TALLY

assert_equal expected, Tournament.tally(input)
end
end``````

### tournament.rb

``````# frozen_string_literal: true

require_relative "tournament/parse_input"
require_relative "tournament/create_matches"
require_relative "tournament/build_tally"
require_relative "tournament/build_table"

module Tournament
POINTS = {
won: 3,
drawn: 1,
lost: 0
}.freeze

def self.tally(input)
parsed_input = ParseInput.call(input)
matches = CreateMatches.call(parsed_input)
tally = BuildTally.call(matches)

BuildTable.call(tally)
end
end``````

### tournament/parse_input.rb

``````# frozen_string_literal: true

module Tournament
class ParseInput
MATCH_RESULTS = {
win: { team_one: :winner, team_two: :loser },
draw: { team_one: :drawn, team_two: :drawn },
loss: { team_one: :loser, team_two: :winner }
}.freeze

def self.call(input)
new(input).call
end

def initialize(input)
@input = input.each_line(chomp: true).reject(&:empty?)
end

def call
@input.map do |line|
team_one, team_two, result = line.split(";")
result = MATCH_RESULTS[result.to_sym]

next if result.nil?

{ team_one: team_one, team_two: team_two, result: result }
end.compact
end
end
end``````

### tournament/create_matches.rb

``````# frozen_string_literal: true

require_relative "match"

module Tournament
class CreateMatches
def self.call(parsed_input)
parsed_input.map do |match_data|
Match.new(
team_one: match_data[:team_one],
team_two: match_data[:team_two],
result: match_data[:result]
)
end
end
end
end``````

### tournament/match.rb

``````# frozen_string_literal: true

require_relative "match/team"

module Tournament
class Match
def initialize(team_one:, team_two:, result:)
@team_one = team_one
@team_two = team_two
@result = result
end

def teams
[
Team.new(**team_attributes(:team_one)).to_h,
Team.new(**team_attributes(:team_two)).to_h
]
end

private

def team_attributes(team)
team_result = result[team]
team_result_attributes = result_attributes[team_result]

{ name: send(team), played: 1 }.merge(team_result_attributes)
end

def result_attributes
{
winner: { won: 1, points: POINTS[:won] },
drawn: { drawn: 1, points: POINTS[:drawn] },
loser: { lost: 1, points: POINTS[:lost] }
}
end
end
end``````

### tournament/match/team.rb

``````# frozen_string_literal: true

module Tournament
class Match
class Team

ATTRIBUTES = %i[name played won drawn lost points].freeze

def initialize(args = {})
args = { name: "" }.merge(args)

ATTRIBUTES.each do |attr|
instance_variable_set("@#{attr}", args.fetch(attr, 0))
end
end

def to_h
ATTRIBUTES.map { |attr| [attr, instance_variable_get("@#{attr}")] }.to_h
end
end
end
end``````

### tournament/build_tally.rb

``````# frozen_string_literal: true

module Tournament
class BuildTally
def self.call(matches)
new(matches).call
end

def initialize(matches)
@matches = matches
end

def call
teams_data.map { |team, matches| sum_team_data(team, matches) }
end

private

def teams_data
matches.flat_map(&:teams).group_by { |team| team[:name] }
end

def sum_team_data(team, matches)
team_match_attributes
.map { |attr| [attr, attribute_sum(matches, attr)] }.to_h
.merge(name: team)
end

def team_match_attributes
Match::Team::ATTRIBUTES.reject { |attr| attr == :name }
end

def attribute_sum(matches, attr)
matches.sum { |match| match[attr] }
end
end
end``````

### tournament/build_table.rb

``````# frozen_string_literal: true

module Tournament
class BuildTable
COLUMNS_JUSTIFICATION = {
0 => [:ljust, 30],
1 => [:rjust, 2],
2 => [:rjust, 2],
3 => [:rjust, 2],
4 => [:rjust, 2],
5 => [:rjust, 2]
}.freeze

DATA = {
name: "Team",
played: "MP",
won: "W",
drawn: "D",
lost: "L",
points: "P"
}.freeze

def self.call(teams_data)
new(teams_data).call
end

def initialize(teams_data)
@teams_data = teams_data
@result = []
end

def call
format_result
end

private

attr_accessor :result

end

def format_row(row)
row.map.with_index do |column, index|
column.to_s.send(*COLUMNS_JUSTIFICATION[index])
end.join(" | ")
end

result << team_rows.map { |team_row| format_row(team_row) }
end

def team_rows
sorted_team_data.map { |team| team.slice(*DATA.keys).values }
end

def sorted_team_data
teams_data
.sort_by { |team| team[:name] }.reverse
.sort_by { |team| team[:points] }.reverse
end

def format_result
result.flatten.join("\n") + "\n"
end
end
end``````