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?
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.
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
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
require 'minitest/autorun'
require_relative 'tournament'
# Common test data version: 1.4.0 ee01fe0
class TournamentTest < Minitest::Test
def test_just_the_header_if_no_input
# 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
Allegoric Alaskans;Blithering Badgers;win
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
Blithering Badgers;Allegoric Alaskans;loss
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
Blithering Badgers;Allegoric Alaskans;win
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
Allegoric Alaskans;Blithering Badgers;draw
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
Allegoric Alaskans;Blithering Badgers;win
Allegoric Alaskans;Blithering Badgers;win
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
Allegoric Alaskans;Blithering Badgers;loss
Allegoric Alaskans;Blithering Badgers;win
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
Allegoric Alaskans;Blithering Badgers;win
Blithering Badgers;Courageous Californians;win
Courageous Californians;Allegoric Alaskans;loss
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
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
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
Allegoric Alaskans;Blithering Badgers;loss
Devastating Donkeys;Allegoric Alaskans;loss
Courageous Californians;Blithering Badgers;draw
Allegoric Alaskans;Courageous Californians;win
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
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
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
# 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
# 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
# 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
# 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
attr_reader :team_one, :team_two, :result
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
# frozen_string_literal: true
module Tournament
class Match
class Team
attr_reader :name
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
# 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
attr_reader :matches
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
# 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
HEADER = DATA.values
def self.call(teams_data)
new(teams_data).call
end
def initialize(teams_data)
@teams_data = teams_data
@result = []
end
def call
add_header
add_team_rows
format_result
end
private
attr_reader :teams_data
attr_accessor :result
def add_header
result << format_row(HEADER)
end
def format_row(row)
row.map.with_index do |column, index|
column.to_s.send(*COLUMNS_JUSTIFICATION[index])
end.join(" | ")
end
def add_team_rows
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
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.
Level up your programming skills with 3,450 exercises across 52 languages, and insightful discussion with our volunteer team of welcoming mentors. Exercism is 100% free forever.
Sign up Learn More
Community comments