trainworks 1.0.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 +7 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +14 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +112 -0
- data/Rakefile +6 -0
- data/examples/input.txt +1 -0
- data/lib/trainworks.rb +10 -0
- data/lib/trainworks/file_parser.rb +40 -0
- data/lib/trainworks/file_parser/invalid_railroad_input_format.rb +18 -0
- data/lib/trainworks/graph_algorithm.rb +149 -0
- data/lib/trainworks/graph_builder.rb +31 -0
- data/lib/trainworks/railroad.rb +53 -0
- data/lib/trainworks/route.rb +28 -0
- data/lib/trainworks/version.rb +4 -0
- data/trainworks.gemspec +27 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bd0bbc94d01b0526100339105b0e244a47c84286
|
4
|
+
data.tar.gz: e1187451608b2347fe70790753498d9158bd65a3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 893e1e95c8ba60a8722ddc686587a2bca8edc7a74e05e80d3440c005330ee3b7b629b80676edcd748a78f40c4d4f8061a280711bf2078e9ce9e23f957e34049a
|
7
|
+
data.tar.gz: 10385ce964d3426719dcc6a2acc86f4685fb6e774fb93ed4b0be8f84dd017d0dc81c2b28a59f73b4531d71dfd4979637ed78661af0dc3e58f5c220fd1ae0209f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Metrics/LineLength:
|
2
|
+
Max: 120
|
3
|
+
SpaceInsideHashLiteralBraces:
|
4
|
+
Enabled: false
|
5
|
+
SpaceInsideBrackets:
|
6
|
+
Enabled: false
|
7
|
+
Style/FrozenStringLiteralComment:
|
8
|
+
Enabled: false
|
9
|
+
Style/FrozenStringLiteralComment:
|
10
|
+
Enabled: false
|
11
|
+
Metrics/BlockLength:
|
12
|
+
Enabled: true
|
13
|
+
Exclude:
|
14
|
+
- spec/**/*
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
trainworks
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.3
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup=markdown
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Andre Herculano
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# Trainworks 🚂
|
2
|
+
[](https://semaphoreci.com/andresilveirah/trainworks)
|
3
|
+
|
4
|
+
## Problem Definition
|
5
|
+
|
6
|
+
The local commuter railroad services a number of towns in Kiwiland. Because of monetary concerns, all of the tracks are 'one-way.' That is, a route from Kaitaia to Invercargill does not imply the existence of a route from Invercargill to Kaitaia. In fact, even if both of these routes do happen to exist, they are distinct and are not necessarily the same distance!
|
7
|
+
|
8
|
+
The purpose of this problem is to help the railroad provide its customers with information about the routes. In particular, you will compute the distance along a certain route, the number of different routes between two towns, and the shortest route between two towns.
|
9
|
+
|
10
|
+
### Input
|
11
|
+
A directed graph where a node represents a town and an edge represents a route between two towns. The weighting of the edge represents the distance between the two towns. A given route will never appear more than once, and for a given route, the starting and ending town will not be the same town.
|
12
|
+
|
13
|
+
### Output
|
14
|
+
|
15
|
+
For test input 1 through 5, if no such route exists, output 'NO SUCH ROUTE'. Otherwise, follow the route as given; do not make any extra stops! For example, the first problem means to start at city A, then travel directly to city B (a distance of 5), then directly to city C (a distance of 4).
|
16
|
+
|
17
|
+
1. The distance of the route A-B-C.
|
18
|
+
2. The distance of the route A-D.
|
19
|
+
3. The distance of the route A-D-C.
|
20
|
+
4. The distance of the route A-E-B-C-D.
|
21
|
+
5. The distance of the route A-E-D.
|
22
|
+
6. The number of trips starting at C and ending at C with a maximum of 3 stops. In the sample data below, there are two such trips: C-D-C (2 stops). and C-E-B-C (3 stops).
|
23
|
+
7. The number of trips starting at A and ending at C with exactly 4 stops. In the sample data below, there are three such trips: A to C (via B,C,D); A to C (via D,C,D); and A to C (via D,E,B).
|
24
|
+
8. The length of the shortest route (in terms of distance to travel) from A to C.
|
25
|
+
9. The length of the shortest route (in terms of distance to travel) from B to B.
|
26
|
+
10. The number of different routes from C to C with a distance of less than 30. In the sample data, the trips are: CDC, CEBC, CEBCDC, CDCEBC, CDEBC, CEBCEBC, CEBCEBCEBC.
|
27
|
+
|
28
|
+
### Test Input
|
29
|
+
|
30
|
+
For the test input, the towns are named using the first few letters of the alphabet from A to E. A route between two towns (A to B) with a distance of 5 is represented as AB5.
|
31
|
+
|
32
|
+
Graph: AB5, BC4, CD8, DC8, DE6, AD5, CE2, EB3, AE7
|
33
|
+
|
34
|
+
Expected Output:
|
35
|
+
|
36
|
+
```
|
37
|
+
Output #1: 9
|
38
|
+
Output #2: 5
|
39
|
+
Output #3: 13
|
40
|
+
Output #4: 22
|
41
|
+
Output #5: NO SUCH ROUTE
|
42
|
+
Output #6: 2
|
43
|
+
Output #7: 3
|
44
|
+
Output #8: 9
|
45
|
+
Output #9: 9
|
46
|
+
Output #10: 7
|
47
|
+
```
|
48
|
+
|
49
|
+
## Installation
|
50
|
+
|
51
|
+
**Note:** Since the project is not yet publicly available, there's no way to fork/clone/install it from [Rubygems](https://rubygems.org/). The only way to use the gem is getting its source code and running `rake install`. It will install the local gem so you'll be able to require it in `irb` for example.
|
52
|
+
|
53
|
+
First you'll need [Bundler](http://bundler.io/) in order to install the dependencies (which are only necessary for development).
|
54
|
+
|
55
|
+
$ gem install bundler
|
56
|
+
|
57
|
+
Add this line to your application's Gemfile:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
gem 'trainworks'
|
61
|
+
```
|
62
|
+
|
63
|
+
And then execute:
|
64
|
+
|
65
|
+
$ bundle
|
66
|
+
|
67
|
+
Or install it yourself as:
|
68
|
+
|
69
|
+
$ gem install trainworks
|
70
|
+
|
71
|
+
## Usage
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
require 'trainworks'
|
75
|
+
|
76
|
+
railroad = Trainworks::Railroad.new('examples/input.txt')
|
77
|
+
|
78
|
+
# assuming you have the following graph
|
79
|
+
# AB5, BC4, CD8, DC8, DE6, AD5, CE2, EB3, AE7
|
80
|
+
railroad.distance("A-B-C")
|
81
|
+
# => 9.0
|
82
|
+
|
83
|
+
railroad.distance("A-Z")
|
84
|
+
# => "NO SUCH ROUTE"
|
85
|
+
|
86
|
+
railroad.shortest_distance(from: "A", to: "C")
|
87
|
+
# => 9.0
|
88
|
+
|
89
|
+
railroad.trips(from: "C", to: "C", with_max_stops: 3)
|
90
|
+
# => [["C","D","C"], ["C","E","B","C"]]
|
91
|
+
|
92
|
+
railroad.trips(from: "A", to: "C", with_exact_stops: 4)
|
93
|
+
# => [["A", "B", "C", "D", "C"], ["A", "D", "C", "D", "C"], ["A", "D", "E", "B", "C"]]
|
94
|
+
|
95
|
+
railroad.trips(from: "C", to: "C", with_max_distance: 30)
|
96
|
+
# => [["C","D","C"], ["C","E","B","C"], ["C","E","B","C","D","C"], ["C","D","C","E","B","C"], ["C","D","E","B","C"], ["C","E","B","C","E","B","C"], ["C","E","B","C","E","B","C","E","B","C"]]
|
97
|
+
```
|
98
|
+
|
99
|
+
## Development & Contribution
|
100
|
+
|
101
|
+
**Note:** This section is only valid after the project is publicly available.
|
102
|
+
|
103
|
+
1. Fork this repository.
|
104
|
+
2. Clone it into your machine and run `bundle install`.
|
105
|
+
3. Add your changes.
|
106
|
+
4. Make sure the specs are green by running `rspec`.
|
107
|
+
5. Make sure the code style is respected by running `rubocop`.
|
108
|
+
6. Open a pull request.
|
109
|
+
|
110
|
+
## License
|
111
|
+
|
112
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/examples/input.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
AB5, BC4, CD8, DC8, DE6, AD5, CE2, EB3, AE7
|
data/lib/trainworks.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'trainworks/version'
|
2
|
+
require 'trainworks/railroad'
|
3
|
+
require 'trainworks/file_parser'
|
4
|
+
require 'trainworks/route'
|
5
|
+
require 'trainworks/graph_algorithm'
|
6
|
+
require 'trainworks/graph_builder'
|
7
|
+
|
8
|
+
# Trainworks is used only as a namespace for the trainworks gem
|
9
|
+
module Trainworks
|
10
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'trainworks/file_parser/invalid_railroad_input_format'
|
2
|
+
|
3
|
+
module Trainworks
|
4
|
+
# FileParser is responsible for parsing the input file
|
5
|
+
# @example
|
6
|
+
# AB5, cd99, LetterLetterPositiveNumbers
|
7
|
+
class FileParser
|
8
|
+
# captures, for example, AB99
|
9
|
+
SINGLE_TUPLE_REGEX = /(?<from>[a-zA-Z])(?<to>[a-zA-Z])(?<distance>\d+)/
|
10
|
+
|
11
|
+
# Converts the object into textual markup given a specific format.
|
12
|
+
# @param file_path [String] the path for input file
|
13
|
+
def initialize(file_path)
|
14
|
+
@raw_content = File.read(file_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Array<Route>] array of {Route}s
|
18
|
+
# @raise [InvalidRailroadInputFormat] if the tuple doesn't match SINGLE_TUPLE_REGEX
|
19
|
+
def parse
|
20
|
+
clean_string(@raw_content).split(',').map do |route_string|
|
21
|
+
matched_route_string = route_string.match(SINGLE_TUPLE_REGEX)
|
22
|
+
raise InvalidRailroadInputFormat, route_string if matched_route_string.nil?
|
23
|
+
|
24
|
+
Route.new(
|
25
|
+
from: matched_route_string[:from],
|
26
|
+
to: matched_route_string[:to],
|
27
|
+
distance: matched_route_string[:distance]
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# removes special characters from the input except letters, numbers and commas
|
35
|
+
# @private
|
36
|
+
def clean_string(text)
|
37
|
+
text.gsub(/[^0-9A-Za-z\,]/, '')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Trainworks
|
2
|
+
class FileParser
|
3
|
+
# When parsing the input file, format of the route is not correct
|
4
|
+
# InvalidRailroadInputFormat will be raised
|
5
|
+
class InvalidRailroadInputFormat < ArgumentError
|
6
|
+
# @param [Object] route_string - must respond to `#to_s`
|
7
|
+
# @return [InvalidRailroadInputFormat]
|
8
|
+
def initialize(route_string)
|
9
|
+
@route_string = route_string
|
10
|
+
end
|
11
|
+
|
12
|
+
# Converts the exception into string
|
13
|
+
def to_s
|
14
|
+
"'#{@route_string}' is not of the form LetterLetterNumber. E.g. AB10"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# rubocop:disable MethodLength
|
2
|
+
# rubocop:disable ParameterLists
|
3
|
+
|
4
|
+
module Trainworks
|
5
|
+
# GraphAlgorithm is used to calculate direct distances,
|
6
|
+
# shortest path and other paths between nodes.
|
7
|
+
# It assumes graph is a hash of adjacencies of a **directed-positively-weighted-possibly-cyclic-graph**
|
8
|
+
# @example Example of a graph
|
9
|
+
# 'A' => { 'B' => 5 },
|
10
|
+
# 'B' => { 'C' => 5 },
|
11
|
+
# 'C' => { 'B' => 4 }
|
12
|
+
#
|
13
|
+
class GraphAlgorithm
|
14
|
+
def initialize(graph)
|
15
|
+
@graph = graph
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [Array] cities is a set of nodes
|
19
|
+
# @return [Number] the distance traveled from city to city in cities
|
20
|
+
# @return [String] "NO SUCH ROUTE" when one of the citis is not connected to another
|
21
|
+
# @example
|
22
|
+
# algorithm.distance(['A', 'B', 'C']) => 10
|
23
|
+
def distance(cities)
|
24
|
+
distance = 0
|
25
|
+
begin
|
26
|
+
cities.each_cons(2) do |(from, to)|
|
27
|
+
distance += go(from: from, to: to)
|
28
|
+
end
|
29
|
+
rescue NoSuchRoute => e
|
30
|
+
distance = e.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
distance
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [Object] from the starting point
|
37
|
+
# @param [Object] to the goal
|
38
|
+
# @param [Number] stops the maximum number of stops between from and to the algorithm is allowed to travel
|
39
|
+
# @return [Array<Array>] all possible trips from the starting point to the goal with maximum stops equal to `stops`
|
40
|
+
def trips_with_max_stops(from:, to:, stops:, total_paths: [from], solutions: [])
|
41
|
+
routes(from).map do |city, _paths|
|
42
|
+
return solutions if 0 >= stops
|
43
|
+
next_total_paths = [*total_paths, city]
|
44
|
+
solutions.push(next_total_paths) if arrived?(city, to)
|
45
|
+
trips_with_max_stops(from: city, to: to, stops: stops - 1, total_paths: next_total_paths, solutions: solutions)
|
46
|
+
end
|
47
|
+
solutions
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [Object] from the starting point
|
51
|
+
# @param [Object] to the goal
|
52
|
+
# @param [Number] stops the maximum number of stops between from and to the algorithm is allowed to travel
|
53
|
+
# @return [Array<Array>] all possible trips from the starting point to the goal with maximum stops equal to `stops`
|
54
|
+
# @see GraphAlgorithm#trips_with_max_stops #trips_with_max_stops
|
55
|
+
# it calls {trips_with_max_stops} and filter out the trips that don't have the exact number of stops as `stops`
|
56
|
+
def trips_with_exact_stops(from:, to:, stops:)
|
57
|
+
total_path_size = stops + 1 # a path includes the stops plus the origin
|
58
|
+
trips_with_max_stops(from: from, to: to, stops: stops).select do |path|
|
59
|
+
path.size == total_path_size
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [Object] from the starting point
|
64
|
+
# @param [Object] to the goal
|
65
|
+
# @param [Number] max_distance the maximum distance between from and to the algorithm is allowed to travel
|
66
|
+
# @return [Array<Array>] all trips from the starting point to the goal with maximum stops equal to `max_distance`
|
67
|
+
# Since we calculate the distance by adding up the distances between the nodes, the edges must have positive
|
68
|
+
# distances otherwise it's not garateed the algorithm will reach a stop.
|
69
|
+
def trips_with_max_distance(from:, to:, max_distance:, total_paths: [from], solutions: {}, current_distance: 0)
|
70
|
+
routes(from).map do |city, _paths|
|
71
|
+
next_current_distance = current_distance + go(from: from, to: city)
|
72
|
+
return solutions.keys if next_current_distance >= max_distance
|
73
|
+
next_total_paths = [*total_paths, city]
|
74
|
+
solutions[next_total_paths] = next_current_distance if arrived?(city, to)
|
75
|
+
trips_with_max_distance(
|
76
|
+
from: city,
|
77
|
+
to: to,
|
78
|
+
max_distance: max_distance,
|
79
|
+
total_paths: next_total_paths,
|
80
|
+
solutions: solutions,
|
81
|
+
current_distance: next_current_distance
|
82
|
+
)
|
83
|
+
end
|
84
|
+
solutions.keys
|
85
|
+
end
|
86
|
+
|
87
|
+
# @param [Object] from the starting point
|
88
|
+
# @param [Object] to the goal
|
89
|
+
# @return [Hash] all possible trips from the starting point to the goal without repeating loops
|
90
|
+
def trips(from:, to:, current_distance: 0, current_path: [from], all_paths: {})
|
91
|
+
routes(from).map do |city, _paths|
|
92
|
+
next_current_path = [*current_path, city]
|
93
|
+
next_current_distance = current_distance + go(from: from, to: city)
|
94
|
+
all_paths[next_current_path] = next_current_distance if arrived?(city, to)
|
95
|
+
next if arrived?(city, to) || already_visited?(next_current_path, city)
|
96
|
+
trips(
|
97
|
+
from: city,
|
98
|
+
to: to,
|
99
|
+
current_distance: next_current_distance,
|
100
|
+
current_path: next_current_path,
|
101
|
+
all_paths: all_paths
|
102
|
+
)
|
103
|
+
end
|
104
|
+
all_paths
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param [Object] from the starting point
|
108
|
+
# @param [Object] to the goal
|
109
|
+
# @return [Number] the shortest distance from `from` to `to`
|
110
|
+
# @see GraphAlgorithm#trips #trips
|
111
|
+
# @raise NoSuchRoute
|
112
|
+
# It calls {trips} to find all paths between the starting point and the goal and returns
|
113
|
+
# the shortest distance found. If there's no path between `from` and `to` it raises {NoSuchRoute}
|
114
|
+
def shortest_distance(from:, to:)
|
115
|
+
shortest_distance = trips(from: from, to: to).values.min
|
116
|
+
raise NoSuchRoute if shortest_distance.nil?
|
117
|
+
shortest_distance
|
118
|
+
end
|
119
|
+
|
120
|
+
# NoSuchRoute is raised when there are no direct connection between two cities (nodes)
|
121
|
+
class NoSuchRoute < KeyError
|
122
|
+
def to_s
|
123
|
+
'NO SUCH ROUTE'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
def arrived?(current, goal)
|
130
|
+
current == goal
|
131
|
+
end
|
132
|
+
|
133
|
+
# if the current_city can be found more than one time in the path
|
134
|
+
# it means we already visited it.
|
135
|
+
def already_visited?(path, current_city)
|
136
|
+
path.count(current_city) > 1
|
137
|
+
end
|
138
|
+
|
139
|
+
# these are helper methods to deal with finding keys in the graph an its value
|
140
|
+
# in case of not finding the keys (`from` and `to`) it raises `NoSuchRoute`
|
141
|
+
def go(from:, to:)
|
142
|
+
routes(from).fetch(to) { raise NoSuchRoute }
|
143
|
+
end
|
144
|
+
|
145
|
+
def routes(city)
|
146
|
+
@graph.fetch(city) { raise NoSuchRoute }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Trainworks
|
2
|
+
# GraphBuilder is responsible to transform tuples (in our case Routes)
|
3
|
+
# into a Hash which represents a list of adjacencies between the nodes.
|
4
|
+
#
|
5
|
+
# @todo: encapsulate the idea of a Graph inside a class instead of relying on Hash class.
|
6
|
+
module GraphBuilder
|
7
|
+
# @param [Array<Route>] routes
|
8
|
+
# @return [Hash]
|
9
|
+
def self.build(routes)
|
10
|
+
routes.reduce({}) do |graph, route|
|
11
|
+
add_edge(graph, route.from, route.to, route.distance)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Hash] graph
|
16
|
+
# @param [Object] from
|
17
|
+
# @param [Object] to
|
18
|
+
# @param [Object] distance
|
19
|
+
# @return [Hash] graph with the new edge
|
20
|
+
# Adds a new edge to the graph.
|
21
|
+
# It checks if the key `from` (*node*) is present in the graph and adds to it the `to` with its distance.
|
22
|
+
def self.add_edge(graph, from, to, distance)
|
23
|
+
if graph.key?(from)
|
24
|
+
graph[from][to] = distance
|
25
|
+
else
|
26
|
+
graph[from] = Hash[to, distance]
|
27
|
+
end
|
28
|
+
graph
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Trainworks
|
2
|
+
# Railroad is the entry point for Trainworks gem
|
3
|
+
class Railroad
|
4
|
+
# @param [String] file_path is the path for the input file containing the graph definition
|
5
|
+
# @param [Object] file_parser is the class responsible for parsing the input file and return a set of {Route}s
|
6
|
+
# @param [Object] graph_builder receives the set of {Route}s and build a graph for `graph_algorithm`
|
7
|
+
# @param [Object] graph_algorithm is the responsible for calculating the distances between nodes, paths, etc.
|
8
|
+
def initialize(file_path, file_parser: FileParser, graph_builder: GraphBuilder, graph_algorithm: GraphAlgorithm)
|
9
|
+
@file_parser = file_parser.new(file_path)
|
10
|
+
@graph = graph_builder.build(@file_parser.parse)
|
11
|
+
@algorithm = graph_algorithm.new(@graph)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [String] route_string represents the route to be followed. E.g. A-B-C
|
15
|
+
# @return [Number] the distance from A to B to C
|
16
|
+
# Calculates the distance traveled between cities in the form of `"A-B-C"`
|
17
|
+
def distance(route_string)
|
18
|
+
@algorithm.distance(route_string.split('-'))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Object] from is the starting point
|
22
|
+
# @param [Object] to is the goal
|
23
|
+
# @param [Number] with_max_stops how many stops at most, the algorithm is allowed to *travel*.
|
24
|
+
# @param [Number] with_exact_stops how many stops exactly, the algorithm is allowed to *travel*.
|
25
|
+
# @param [Number] with_max_distance the maximum distance (non inclusive), the algorithm is allowed to *travel*
|
26
|
+
# @return [Array<Array>] which represents each path found
|
27
|
+
# Notice that only one of the parameters `with_max_stops`, `with_exact_stops` or `with_max_distance` will be taken
|
28
|
+
# into account, in the following order:
|
29
|
+
#
|
30
|
+
# 1. `with_max_stops`
|
31
|
+
# 2. `with_exact_stops`
|
32
|
+
# 3. `with_max_distance`
|
33
|
+
def trips(from:, to:, with_max_stops: nil, with_exact_stops: nil, with_max_distance: nil)
|
34
|
+
if with_max_stops
|
35
|
+
@algorithm.trips_with_max_stops(from: from, to: to, stops: with_max_stops)
|
36
|
+
elsif with_exact_stops
|
37
|
+
@algorithm.trips_with_exact_stops(from: from, to: to, stops: with_exact_stops)
|
38
|
+
elsif with_max_distance
|
39
|
+
@algorithm.trips_with_max_distance(from: from, to: to, max_distance: with_max_distance)
|
40
|
+
else
|
41
|
+
raise "I don't know how to calculate these routes"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @param [Object] from is the starting point
|
46
|
+
# @param [Object] to is the goal
|
47
|
+
# @return [Number] the shortest distance between `from` and `to`
|
48
|
+
# Calculates the shortest distance between `from` and `to`
|
49
|
+
def shortest_distance(from:, to:)
|
50
|
+
@algorithm.shortest_distance(from: from, to: to)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Trainworks
|
2
|
+
# Route is used to group together the information
|
3
|
+
# of an edge of a graph. From and To are two nodes
|
4
|
+
# and distance is the weight between them
|
5
|
+
class Route
|
6
|
+
attr_accessor :from, :to, :distance
|
7
|
+
|
8
|
+
# @param [Object] from is the starting point of the route
|
9
|
+
# @param [Object] to is the end point of the route
|
10
|
+
# @param [Object] distance also known as *weight* between `from` and `to`
|
11
|
+
# @return [Route]
|
12
|
+
# `from` and `to` must respond to `#to_s` and `distance` must respond to `#to_f`
|
13
|
+
def initialize(from:, to:, distance:)
|
14
|
+
self.from = from.to_s
|
15
|
+
self.to = to.to_s
|
16
|
+
self.distance = distance.to_f
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Object] other
|
20
|
+
# @return [Boolean]
|
21
|
+
# Returns `true` if `other.from`, `other.to` and `other.distance` have the same value as `self`
|
22
|
+
def ==(other)
|
23
|
+
from == other.from &&
|
24
|
+
to == other.to &&
|
25
|
+
distance == other.distance
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/trainworks.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'trainworks/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'trainworks'
|
9
|
+
spec.version = Trainworks::VERSION
|
10
|
+
spec.authors = ['André Herculano']
|
11
|
+
spec.email = ['andresilveirah@gmail.com']
|
12
|
+
spec.homepage = 'https://rubygems.org/gems/example'
|
13
|
+
spec.summary = 'Models a railroad as a graph and offers some traversing methods.'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(spec)/})
|
18
|
+
end
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
24
|
+
spec.add_development_dependency 'rubocop', '~> 0.45.0'
|
25
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
26
|
+
spec.add_development_dependency 'redcarpet', '~> 3.3'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trainworks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- André Herculano
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.45.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.45.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.9'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: redcarpet
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.3'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- andresilveirah@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".rubocop.yml"
|
107
|
+
- ".ruby-gemset"
|
108
|
+
- ".ruby-version"
|
109
|
+
- ".yardopts"
|
110
|
+
- Gemfile
|
111
|
+
- LICENSE.txt
|
112
|
+
- README.md
|
113
|
+
- Rakefile
|
114
|
+
- examples/input.txt
|
115
|
+
- lib/trainworks.rb
|
116
|
+
- lib/trainworks/file_parser.rb
|
117
|
+
- lib/trainworks/file_parser/invalid_railroad_input_format.rb
|
118
|
+
- lib/trainworks/graph_algorithm.rb
|
119
|
+
- lib/trainworks/graph_builder.rb
|
120
|
+
- lib/trainworks/railroad.rb
|
121
|
+
- lib/trainworks/route.rb
|
122
|
+
- lib/trainworks/version.rb
|
123
|
+
- tmp/.gitkeep
|
124
|
+
- trainworks.gemspec
|
125
|
+
homepage: https://rubygems.org/gems/example
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.6.11
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: Models a railroad as a graph and offers some traversing methods.
|
149
|
+
test_files: []
|