cool_soccer 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b3fd94f126775096201987af7f36fb3d66d9edb50e514b536c1af8d92cd5d68
4
- data.tar.gz: ec3b056a6abe39c7f130d9d306b4e50e031e82b9c0c39ecc0c686cd5eb2d0019
3
+ metadata.gz: f9c5dd5b67b28fb8a8cb8e6eeced481d22f2a83d8848bd64c0c0ea4a59e91d7e
4
+ data.tar.gz: 8f7d990484715db577b1da34959bfdade276d66440c2122ed2221b11d8ef8539
5
5
  SHA512:
6
- metadata.gz: 3854a4121220bc1b298bd1747801a5d087d780a2d37321bb1f9786bc0fd47be11d82faae40baa4288c50945359255197e2e99f09d12859bdcd14b0fa3bb7e547
7
- data.tar.gz: f0725e89a483f9ac95819eb1a32cff5e5b6864caabbb7c35411ff0c40d537c6974d764ce842a539240f540588f3d10dfc35d8a8b471cdeed924fcd4361aaebe4
6
+ metadata.gz: 15e7bf21c7578cf1af656c2279916a48100b56e3ae7bae65456d3fa05ff1d6f5f15a6989192ddd594752e3e5aaecab2f413a08a51530d8c2e221896e25d9c4c8
7
+ data.tar.gz: f34e9ae6c2f8991a5e7d6fdb289f78a39722daf7f7590727269f95fce52c01b22541c53f61fde2543e75aa44add585e0061b383c15f0b92ed3535eadcc208a0c
data/bin/cool_soccer CHANGED
@@ -3,19 +3,31 @@
3
3
 
4
4
  require_relative '../lib/cool_soccer'
5
5
 
6
+ # If there are more than one arguments, exit with an error message.
7
+ if ARGV.length > 1
8
+ puts 'Too many arguments. Expected to be passed a stream, or a path to a single file'
9
+ exit 1
10
+ end
11
+
12
+ # If the file at ARGV[0] does not exist, exit with an error message.
13
+ if ARGV[0] && !File.exist?(ARGV[0])
14
+ puts "The file #{ARGV[0]} does not exist"
15
+ exit 1
16
+ end
17
+
6
18
  app = CoolSoccer.new
7
19
 
8
- if !ARGV.empty?
9
- file_path = ARGV[0]
10
- File.foreach(file_path) do |line|
20
+ if ARGV.empty?
21
+ $stdin.each_line do |line|
11
22
  app.execute(line: line)
12
23
  end
13
24
  else
14
- $stdin.each_line do |line|
25
+ file_path = ARGV[0]
26
+ File.foreach(file_path) do |line|
15
27
  app.execute(line: line)
16
28
  end
17
-
18
- # If we haven't reset the games_today to 0, we stopped before the end of a match day,
19
- # so we need to print out the current top three
20
29
  end
21
- puts app.format_match_day(day: app.match_day, leaderboard: app.leaderboard) if app.games_today.positive?
30
+
31
+ # If the input stops, we should print the final standings as the last match day,
32
+ # since it will be incomplete.
33
+ puts app.format_match_day(day: app.match_day, leaderboard: app.leaderboard)
@@ -16,22 +16,12 @@ module SoccerHelpers
16
16
  # Valid: "San Jose Earthquakes 3, Santa Cruz Slugs 3",
17
17
  # Invalid: "San Jose Earthquakes 3", "San Jose Earthquakes, Santa Cruz Slugs",
18
18
  #
19
+ # We validate the input string with a regular expression, /([a-z-A-Z ]+)(\d+), ([a-zA-Z ]+)(\d+)/
20
+ #
19
21
  # @param result [String] the string to validate
20
22
  # @return [Boolean] is the input string a valid game result string?
21
23
  def valid_game_result?(result:)
22
- # Make sure this string has two parts, delimited by a comment
23
- parts = result.split(',')
24
- return false if parts.length != 2
25
-
26
- # For each part, make sure it contains some numeric value, the value is an integer,
27
- # and we have a team name that consists of alphabetic characters.
28
- parts.each do |part|
29
- return false if part.count('0-9').zero?
30
- return false if part.count('a-zA-Z').zero?
31
- end
32
-
33
- # If we have passed all of the above checks, then the string is a valid game result
34
- true
24
+ result.match?(/^([a-zA-Z ]+)(\d+), ([a-zA-Z ]+)(\d+)$/)
35
25
  end
36
26
 
37
27
  # extract_team_and_score answers the question:
@@ -68,7 +58,7 @@ module SoccerHelpers
68
58
  end
69
59
 
70
60
  # Zip the team names and scores together into a hash
71
- Hash[team_names.zip(scores)]
61
+ team_names.zip(scores).to_h
72
62
  end
73
63
 
74
64
  # top_three answers the question: "What are the top three teams in the league?"
@@ -78,13 +68,17 @@ module SoccerHelpers
78
68
  # param scores [Hash] a hash of team names and scores
79
69
  # @return [Array] an array of the top three teams, in the format [team, score]
80
70
  def top_three(scores:)
81
- # Alphabetize by team name
82
- scores = scores.sort_by { |name, _| name }.reverse
71
+ # Each item in scores has a key that corresponds to the team name,
72
+ # and a value that corresponds to the score.
73
+ # We want to sort the scores in descending order,
74
+ # and then take the top three scores.
75
+ #
76
+ # If there are any ties in the score, we want to return the tied teams in alphabetical order.
83
77
 
84
- # Now we need to find the top three teams by score
85
- sorted_scores = scores.sort_by { |_, score| score }.reverse
78
+ # sort_by allows us to do a keyed sort over a hash, with multiple criteria
79
+ sorted_scores = scores.sort_by { |name, score| [-score, name] }
86
80
 
87
- # Return only the top three
81
+ # Just return the top three from the sorted order
88
82
  sorted_scores[0..2]
89
83
  end
90
84
 
@@ -138,35 +132,18 @@ module SoccerHelpers
138
132
  end
139
133
 
140
134
  # match_day_over answers the question: "Is the match day over?"
141
- # There are two scenarios where the match day is over:
142
- # 1. It is match day 1, and the leaderboard already has a key that matches one of the input scores teams
143
- # 2. It is any match day past 1, and the number of games today is equal to num_teams / 2
135
+ # It takes two Sets, teams_in_day, which represents the teams we've seen in a current day,
136
+ # and teams_in_match, which represents the teams from a match string
144
137
  #
145
- # This function takes a lot of parameters, which I don't love,
146
- # but I prefer to call one function, rather than have two similar functions,
147
- # (one for match day 1, and one for match day 2 and beyond).
138
+ # Then it returns whether or not any of the teams in teams_in_match already exist in teams_in_day.
148
139
  #
149
- # Using named params helps make the long parameter list manageable:
150
- # callers will get specific error messages if they pass in the wrong params.
140
+ # There is a different, slightly faster, but less readable solve for this.
141
+ # You can see a different approach in the diffs at https://gitlab.com/coolsoftwaretyler/coding-challenge-soccer/-/merge_requests/1
151
142
  #
152
- # param leaderboard [Hash] the current leaderboard
153
- # param scores [Hash] the scores of the current match day
154
- # param match_day [Integer] the current match day
155
- # param num_teams [Integer] the number of teams in the league
156
- # param games_today [Integer] the number of games played today
157
- # @return [Boolean] is the current match day over?
158
- def match_day_over?(leaderboard:, scores:, match_day:, num_teams:, games_today:)
159
- # If this is the first match day, and the leaderboard already has a key that matches one of the input scores teams,
160
- # then the match day is over
161
- return true if match_day == 1 && leaderboard.key?(scores.keys.first)
162
-
163
- return true if match_day == 1 && leaderboard.key?(scores.keys.last)
164
-
165
- # If this is any match day past 1, and the number of games today is equal to num_leagues / 2,
166
- # then the match day is over
167
- return true if match_day > 1 && games_today == num_teams / 2
168
-
169
- # Otherwise, the match day is not over yet.
170
- false
143
+ # @param teams [Set] the teams in the current match
144
+ # @param teams_in_match [Set] the teams in the current match
145
+ # @return [Boolean] is the match day over?
146
+ def match_day_over?(teams_in_day:, teams_in_match:)
147
+ teams_in_day.intersection(teams_in_match).any?
171
148
  end
172
149
  end
data/lib/cool_soccer.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'cool_soccer/soccer_helpers'
4
+ require 'set'
4
5
 
5
6
  # CoolSoccer is an "imperative shell"
6
7
  # that allows the CLI to read a stream of game results from a soccer league,
@@ -15,39 +16,33 @@ class CoolSoccer
15
16
  @leaderboard = {}
16
17
  # We'll also keep track of the current match day, initialized at 1
17
18
  @match_day = 1
18
- # And we'll want to know how many teams are in the league. At first,
19
- # we'll initialize this at 0.
20
- @num_teams = 0
21
- # Finally, we'll want to know how many games we've seen today,
22
- # which we'll initialize at 0.
23
- @games_today = 0
19
+ # We'll also have a Set that keeps tracks of the teams in a given day
20
+ @teams_in_day = Set.new
24
21
  end
25
22
 
26
23
  def execute(line:)
27
24
  # If the line is invalid, skip it
28
25
  return unless valid_game_result?(result: line)
29
26
 
30
- # Increment the number of games we've played today
31
- @games_today += 1
32
-
33
- # If it's still match day 1, increment the number of teams by 2
34
- @num_teams += 2 if @match_day == 1
35
-
36
27
  # Extract the team names and scores from the line
37
28
  scores = extract_team_and_score(result: line)
38
29
 
30
+ # From the scores, create a new set of team names in the given match
31
+ teams_in_match = Set.new(scores.keys)
32
+
39
33
  # Check if the match day is over
40
- if match_day_over?(leaderboard: @leaderboard, scores: scores, match_day: @match_day, num_teams: @num_teams,
41
- games_today: @games_today)
34
+ if match_day_over?(teams_in_day: @teams_in_day, teams_in_match: teams_in_match)
42
35
 
43
36
  # Print out the current top three
44
37
  puts "#{format_match_day(day: @match_day, leaderboard: @leaderboard)}\n"
45
38
 
46
- # If so, increment the match day and reset the games today counter
39
+ # If so, increment the match day and reset the teams_in_day set
47
40
  @match_day += 1
48
- @games_today = 1
41
+ @teams_in_day = Set.new
49
42
  end
50
43
 
44
+ # Add the teams_in_match to the teams_in_day set
45
+ @teams_in_day.merge(teams_in_match)
51
46
  # Add the scores to the leaderboard
52
47
  @leaderboard = update_leaderboard(leaderboard: @leaderboard, changes: scores)
53
48
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cool_soccer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tyler Williams
@@ -44,6 +44,26 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: 1.29.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: rubocop-rspec
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.11'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.11.1
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '2.11'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.11.1
47
67
  - !ruby/object:Gem::Dependency
48
68
  name: yard
49
69
  requirement: !ruby/object:Gem::Requirement
@@ -73,7 +93,8 @@ files:
73
93
  homepage: https://rubygems.org/gems/cool_soccer
74
94
  licenses:
75
95
  - MIT
76
- metadata: {}
96
+ metadata:
97
+ rubygems_mfa_required: 'true'
77
98
  post_install_message:
78
99
  rdoc_options: []
79
100
  require_paths:
@@ -82,7 +103,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
103
  requirements:
83
104
  - - ">="
84
105
  - !ruby/object:Gem::Version
85
- version: '2.5'
106
+ version: 3.0.0
86
107
  required_rubygems_version: !ruby/object:Gem::Requirement
87
108
  requirements:
88
109
  - - ">="