routr 0.0.1

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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-18mode
9
+ - rbx-19mode
10
+ - ruby-head
11
+ - jruby-head
12
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in routr.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
7
+ gem 'guard'
8
+ gem 'guard-rspec'
9
+
@@ -0,0 +1,8 @@
1
+ guard 'rspec', :all_on_start => true, :all_after_pass => true do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/routr/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
4
+ watch(%r{^lib/routr\.rb$}) { |m| "spec/integration" }
5
+ watch(%r{^lib/routr/interface\.rb$}) { |m| "spec/integration" }
6
+ end
7
+
8
+
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Levente Bagi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
+
@@ -0,0 +1,76 @@
1
+ # Routr
2
+
3
+ [![Build Status](https://secure.travis-ci.org/bagilevi/routr.png)](http://travis-ci.org/bagilevi/routr)
4
+
5
+ Implements Dijkstra's algorithm to find the shortest path in a graph.
6
+
7
+ The cost of an edge can be more general than just a numeric value.
8
+ You can inject a method for calculating the distance from source to the
9
+ next node given the distance from source to the current node and the
10
+ properties of an edge. A possible use case is the one used by PeerPost:
11
+ nodes are people, an edge is a connection between specifying when those
12
+ people regularly meet, you inject a method that calculates when people
13
+ meet next after a specified time.
14
+
15
+ # Usage
16
+
17
+ ```
18
+ class MyInterface
19
+ NETWORK = {
20
+ 'A' => { 'B' => 50, 'C' => 100 },
21
+ 'B' => { 'D' => 50 },
22
+ 'C' => { 'D' => 10 },
23
+ 'D' => {}
24
+ }
25
+
26
+ # This will be used if :initial_distance is not supplied to calculate_route
27
+ def initial_distance
28
+ 0
29
+ end
30
+
31
+ def neighbours_of(node_key)
32
+ NETWORK[node_key].keys
33
+ end
34
+
35
+ def calculate_new_distance(distance_so_far, a_key, b_key)
36
+ distance_so_far + NETWORK[a_key][b_key]
37
+ end
38
+ end
39
+
40
+ route = Routr.new(MyInterface.new).calculate_route(
41
+ :from => 'A',
42
+ :to => 'D',
43
+ :initial_distance => 0
44
+ )
45
+
46
+ route.hops.count # => 2
47
+ route.members.count # => 3
48
+
49
+ route.hops.first.from # => 'A'
50
+ route.hops.first.to # => 'B'
51
+ route.hops.first.distance # => 50
52
+ route.hops.last.from # => 'B'
53
+ route.hops.last.to # => 'D'
54
+ route.hops.last.distance # => 100
55
+
56
+ route.members.first # => 'A'
57
+ route.members.last # => 'D'
58
+
59
+ route.distance # => 100
60
+ ```
61
+
62
+ If you have normal addible edge costs, you can replace the
63
+ `calculate_new_distance` method with `edge_distance`:
64
+
65
+ ```
66
+ def edge_cost(a_key, b_key)
67
+ NETWORK[a_key][b_key]
68
+ end
69
+ ```
70
+
71
+ # License
72
+
73
+ Copyright 2012, Levente Bagi
74
+
75
+ Released under the MIT License, see the LICENSE file.
76
+
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc 'Default: run specs.'
6
+ task :default => :spec
7
+
8
+ desc "Run specs"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
11
+ # Put spec opts in a file named .rspec in root
12
+ end
13
+
@@ -0,0 +1,6 @@
1
+ Project.configure do |project|
2
+ project.build_command = <<-COMMAND.split("\n").map(&:strip).join(' ')
3
+ sh -c "env RAILS_ENV=test bundle exec rspec"
4
+ COMMAND
5
+ end
6
+
@@ -0,0 +1,11 @@
1
+ require "routr/version"
2
+
3
+ module Routr
4
+ autoload :Interface, 'routr/interface'
5
+
6
+ class << self
7
+ def new(*args, &block)
8
+ Routr::Interface.new(*args, &block)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,161 @@
1
+ module Routr
2
+ class Calculator
3
+
4
+ def initialize(graph)
5
+ @graph = graph
6
+ end
7
+
8
+ attr_reader :graph
9
+
10
+ # based of wikipedia's pseudocode: http://en.wikipedia.org/wiki/Dijkstra's_algorithm
11
+
12
+ def dijkstra(initial_node, initial_distance = 0)
13
+ debug
14
+ debug "== routing from #{initial_node.inspect}"
15
+ initialize_temporary_variables
16
+
17
+ set_distance(initial_node, initial_distance)
18
+ add_unvisited_node(initial_node)
19
+
20
+ while any_unvisited_nodes?
21
+ u = node_with_minimum_distance
22
+ break if not reachable? u
23
+ mark_as_visited u
24
+
25
+ with_each_neighbour_of u do |v| # for each neighbor v of u: where v has not yet been removed from Q.
26
+ debug "neighbour: #{v}"
27
+ new_distance = calculate_new_distance(best_distance_to(u), u, v)
28
+ if (best_distance_to(v) == :infinity) || (new_distance < best_distance_to(v))
29
+ set_best_distance_to(v, new_distance)
30
+ add_unvisited_node(v)
31
+ set_previous_node_to(v, u)
32
+ end
33
+ end
34
+ debug
35
+ end
36
+ debug "== end routing"
37
+ end
38
+
39
+ def shortest_path src, dest, initial_distance = 0, include_details = false
40
+ dijkstra src, initial_distance
41
+ path_to dest, include_details
42
+ end
43
+
44
+ def path_to(dest, include_details = false)
45
+ if reachable? dest
46
+ item = \
47
+ if include_details
48
+ {
49
+ :node => dest,
50
+ :distance => best_distance_to(dest)
51
+ }
52
+ else
53
+ dest
54
+ end
55
+ previous_path_to(dest, include_details) + [ item ]
56
+ end
57
+ end
58
+
59
+ def previous_path_to(dest, include_details = false)
60
+ if previous_node_to(dest).nil?
61
+ []
62
+ else
63
+ path_to(previous_node_to(dest), include_details)
64
+ end
65
+ end
66
+
67
+
68
+ private
69
+
70
+ def edge_cost a, b
71
+ (graph.edge_cost(a, b) || :infinity).tap { |cost|
72
+ debug "Edge cost #{a} -> #{b} = #{cost.inspect}"
73
+ }
74
+ end
75
+
76
+ def calculate_new_distance distance_so_far, a, b
77
+ if graph.respond_to?(:calculate_new_distance)
78
+ graph.calculate_new_distance distance_so_far, a, b
79
+ else
80
+ distance_so_far + edge_cost(a, b)
81
+ end.tap {|new_distance|
82
+ if new_distance < distance_so_far
83
+ raise "New distance < previous distance: #{new_distance.inspect} < #{distance_so_far.inspect}"
84
+ end
85
+ }
86
+ end
87
+
88
+ def initialize_temporary_variables
89
+ @d = {}
90
+ @prev = {}
91
+ @candidates = []
92
+ end
93
+
94
+ def best_distance_to(node)
95
+ debug "best_distance[#{node.inspect}] # -> #{@d[node].inspect}"
96
+ @d[node] || :infinity
97
+ end
98
+
99
+ def reachable?(node)
100
+ debug "reachable?(#{node.inspect})"
101
+ (best_distance_to(node) != :infinity)\
102
+ .tap {|r| debug "=> #{r.inspect}" }
103
+ end
104
+
105
+
106
+ # OPTIMIZE: maintain a sorted list, so we don't have to search the whole set every time
107
+ def node_with_minimum_distance # unvisited node with minimum distance
108
+ debug "node_with_minimum_distance"
109
+ @candidates.min_by{ |x| best_distance_to(x) }\
110
+ .tap{|x| debug "=> #{x.inspect}"}
111
+ end
112
+
113
+ def mark_as_visited node
114
+ @candidates = @candidates - [node]
115
+ end
116
+
117
+ def any_unvisited_nodes?
118
+ debug "any_unvisited_nodes?"
119
+ @candidates.any?\
120
+ .tap{|r| debug "=> #{r.inspect}" }
121
+ end
122
+
123
+ def add_unvisited_node node
124
+ debug "add_unvisited_node #{node.inspect}"
125
+ @candidates << node
126
+ end
127
+
128
+
129
+ def set_distance node, value
130
+ @d[node] = value # Distance from source to source
131
+ end
132
+
133
+ def with_each_neighbour_of node, &block
134
+ graph.neighbours_of(node).tap{|neighbours| debug "Neighbours of: #{node} -> #{neighbours.inspect}"}.each(&block)
135
+ end
136
+
137
+ def set_best_distance_to node, new_distance
138
+ debug "best_distance[#{node.inspect}] = #{new_distance.inspect}"
139
+ @d[node] = new_distance
140
+ end
141
+
142
+ def set_previous_node_to node, previous_node
143
+ @prev[node] = previous_node
144
+ end
145
+
146
+ def previous_node_to(node)
147
+ @prev[node]
148
+ end
149
+
150
+ def debug *args
151
+ return
152
+ @i ||= 0
153
+ @i += 1
154
+ if @i < 1000
155
+ puts *args
156
+ end
157
+ end
158
+
159
+ end
160
+ end
161
+
@@ -0,0 +1,62 @@
1
+ require 'routr/calculator'
2
+ module Routr
3
+ class Interface
4
+ def initialize interface
5
+ @interface = interface
6
+ end
7
+
8
+ def calculate_route attr
9
+ initial_distance = attr[:initial_distance] ||
10
+ (@interface.initial_distance if @interface.respond_to?(:initial_distance)) ||
11
+ 0
12
+ raw_route = Routr::Calculator.new(@interface)\
13
+ .shortest_path(attr[:from], attr[:to], initial_distance, true)
14
+ route = Route.from_raw(raw_route)
15
+ end
16
+ end
17
+
18
+
19
+ class Route
20
+ attr_reader :hops, :members, :distance, :raw
21
+
22
+ def initialize(raw)
23
+ @raw = raw
24
+ raw ||= []
25
+ @hops = raw[0..-2].to_enum.with_index.map{|item, index|
26
+ Hop.new(
27
+ :from => raw[index][:node],
28
+ :to => raw[index + 1][:node],
29
+ :distance => raw[index + 1][:distance]
30
+ )
31
+ }
32
+ @members = raw.map{|item| item[:node]}
33
+ end
34
+
35
+ def self.from_raw(raw)
36
+ new(raw)
37
+ end
38
+
39
+ def distance
40
+ hops.last.distance
41
+ end
42
+
43
+ def found?
44
+ @hops.any?
45
+ end
46
+
47
+ def missing?
48
+ not found?
49
+ end
50
+ end
51
+
52
+
53
+ class Hop
54
+ attr_reader :from, :to, :distance
55
+ def initialize(attributes)
56
+ @from = attributes[:from]
57
+ @to = attributes[:to]
58
+ @distance = attributes[:distance]
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,3 @@
1
+ module Routr
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "routr/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "routr"
7
+ s.version = Routr::VERSION
8
+ s.authors = ["Levente Bagi"]
9
+ s.email = ["levente@picklive.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Finds the shortest path in a graph, allowing unusual cost definitions}
12
+ s.description = %q{Implements Dijkstra's algorithm to find the shortest path in a graph. The cost of an edge can be more general than just a numeric value.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency "rspec"
20
+ end
@@ -0,0 +1,122 @@
1
+ require 'routr'
2
+
3
+ describe Routr do
4
+ it "works for normal edge costs" do
5
+ class SimpleInterface
6
+ NETWORK = {
7
+ 'A' => { 'B' => 50, 'C' => 100 },
8
+ 'B' => { 'D' => 50 },
9
+ 'C' => { 'D' => 10 },
10
+ 'D' => {}
11
+ }
12
+
13
+ # This will be used if :initial_distance is not supplied to calculate_route
14
+ def initial_distance
15
+ 0
16
+ end
17
+
18
+ def neighbours_of(node_key)
19
+ NETWORK[node_key].keys
20
+ end
21
+
22
+ def edge_cost(a_key, b_key)
23
+ NETWORK[a_key][b_key]
24
+ end
25
+ end
26
+
27
+ route = Routr.new(SimpleInterface.new).calculate_route(
28
+ :from => 'A',
29
+ :to => 'D'
30
+ )
31
+
32
+ route.hops.count.should == 2
33
+ route.members.count.should == 3
34
+
35
+ route.hops.first.from.should == 'A'
36
+ route.hops.first.to.should == 'B'
37
+ route.hops.first.distance.should == 50
38
+ route.hops.last.from.should == 'B'
39
+ route.hops.last.to.should == 'D'
40
+ route.hops.last.distance.should == 100
41
+
42
+ route.members.first.should == 'A'
43
+ route.members.last.should == 'D'
44
+
45
+ route.distance.should == 100
46
+ end
47
+
48
+ it "works for custom edge costs" do
49
+
50
+ # This example uses a simplified version of time.
51
+ #
52
+ # The values denote a time in a day when the two nodes 'meet',
53
+ # where 1000 represents a whole day, i.e.
54
+ #
55
+ # A meets B at 500, 1500, 2500, etc.
56
+ # A meets C at 100, 1100, 2100, etc.
57
+ class CustomInterface
58
+ NETWORK = {
59
+ 'A' => { 'B' => 300, 'C' => 600 },
60
+ 'B' => { 'D' => 400 },
61
+ 'C' => { 'D' => 700 },
62
+ 'D' => {}
63
+ }
64
+
65
+ def neighbours_of(node_key)
66
+ NETWORK[node_key].keys
67
+ end
68
+
69
+ def calculate_new_distance(distance_so_far, a_key, b_key)
70
+ time_of_day = distance_so_far % 1000
71
+ beginning_of_day = distance_so_far - time_of_day
72
+ meeting_time = NETWORK[a_key][b_key]
73
+ if time_of_day <= meeting_time
74
+ beginning_of_day + meeting_time
75
+ else
76
+ beginning_of_day + 1000 + meeting_time
77
+ end
78
+ end
79
+ end
80
+
81
+ route = Routr.new(CustomInterface.new).calculate_route(
82
+ :from => 'A',
83
+ :to => 'D',
84
+ :initial_distance => 50_564
85
+ )
86
+ route.members.should == %w(A C D)
87
+ route.distance.should == 50_700
88
+
89
+ route = Routr.new(CustomInterface.new).calculate_route(
90
+ :from => 'A',
91
+ :to => 'D',
92
+ :initial_distance => 50_900
93
+ )
94
+ route.members.should == %w(A B D)
95
+ route.distance.should == 51_400
96
+ end
97
+
98
+ it "returns a missing route if not found" do
99
+ route = Routr.new(SimpleInterface.new).calculate_route(
100
+ :from => 'D',
101
+ :to => 'A',
102
+ :initial_distance => 0
103
+ )
104
+ route.hops.should == []
105
+ route.members.should == []
106
+ route.found?.should be_false
107
+ route.missing?.should be_true
108
+ end
109
+
110
+ it "exposes the route as raw ruby" do
111
+ route = Routr.new(SimpleInterface.new).calculate_route(
112
+ :from => 'A',
113
+ :to => 'D'
114
+ )
115
+ route.raw.should == [
116
+ { :node => 'A', :distance => 0 },
117
+ { :node => 'B', :distance => 50 },
118
+ { :node => 'D', :distance => 100 },
119
+ ]
120
+ end
121
+ end
122
+
@@ -0,0 +1,105 @@
1
+ require "routr/calculator"
2
+
3
+ describe Routr::Calculator do
4
+ it "calculates shortest path" do
5
+ graph = Graph.new(
6
+ :edges => [
7
+ ["a","b", 5],
8
+ ["b","c", 3],
9
+ ["c","d", 1],
10
+ ["a","d",10],
11
+ ["b","d", 2],
12
+ ["f","g", 1],
13
+ ]
14
+ )
15
+
16
+ r = Routr::Calculator.new(graph)
17
+ r.shortest_path("a", "a").should == %w(a)
18
+ r.shortest_path("a", "b").should == %w(a b)
19
+ r.shortest_path("a", "c").should == %w(a b c)
20
+ r.shortest_path("a", "d").should == %w(a b d)
21
+ r.shortest_path("a", "f").should == nil
22
+ r.shortest_path("a", "g").should == nil
23
+ end
24
+
25
+ it "calculates shortest path for graph with custom costs" do
26
+ graph = TimeGraph.new(
27
+ :edges => [
28
+ ["a","b","1200"], # a and b meet every day at 12:00 noon
29
+ ["b","c","1300"],
30
+ ["a","d","1215"],
31
+ ["d","c","1230"],
32
+ ["c","e","1245"],
33
+ ["f","g","1400"],
34
+ ["a","h","1100"],
35
+ ["h","i","1000"],
36
+ ]
37
+ )
38
+ # h ---1000--- a ---1200--- b
39
+ # | | |
40
+ # 1000 1215 1300
41
+ # | | |
42
+ # i d ---1230--- c
43
+ # |
44
+ # 1245
45
+ # f ---1400--- g |
46
+ # e
47
+ #
48
+ # Meeting times: 4 digits: hhmm (every day)
49
+ # Actual times: 6 digits: ddhhmm
50
+ # 001200 = day 0, 12:00 noon
51
+
52
+ r = Routr::Calculator.new(graph)
53
+ r.shortest_path("a", "a", "001200").should == %w(a)
54
+ r.shortest_path("a", "b", "001200").should == %w(a b)
55
+ r.shortest_path("a", "c", "001200").should == %w(a d c)
56
+ r.shortest_path("a", "d", "001200").should == %w(a d)
57
+ r.shortest_path("a", "e", "001200").should == %w(a d c e)
58
+ r.shortest_path("a", "f", "001200").should == nil
59
+ r.shortest_path("a", "g", "001200").should == nil
60
+ r.shortest_path("a", "h", "001200").should == %w(a h)
61
+ r.shortest_path("a", "i", "001200").should == %w(a h i)
62
+ end
63
+ end
64
+
65
+
66
+ class Graph
67
+ def initialize(attr)
68
+ @edges = {}
69
+ attr[:edges].each do |a, b, edge_info|
70
+ @edges[a] ||= {}
71
+ @edges[a][b] = edge_info
72
+ end
73
+ end
74
+
75
+ def edge_cost a, b
76
+ (@edges[a] && @edges[a][b]) || (@edges[b] && @edges[b][a])
77
+ end
78
+
79
+ def neighbours_of(node)
80
+ (
81
+ ( (@edges[node] && @edges[node].keys) || [] ) +
82
+ @edges.keys.select{|k| @edges[k].keys.include?(node)}
83
+ ).uniq
84
+ end
85
+
86
+ end
87
+
88
+
89
+ class TimeGraph < Graph
90
+ private :edge_cost
91
+
92
+ def calculate_new_distance time_so_far, a, b
93
+ meeting_time_i = edge_cost(a, b).to_i # it's not actually a cost, but that's where we store the meeting value
94
+ time_of_day_so_far_i = time_so_far.to_i % 10000
95
+
96
+ # meeting time today
97
+ new_time_i = time_so_far.to_i + (meeting_time_i - time_of_day_so_far_i)
98
+
99
+ # meeting time tomorrow
100
+ new_time_i += 10000 if time_of_day_so_far_i > meeting_time_i
101
+
102
+ new_time_i.to_s.rjust(6,"0")
103
+ end
104
+ end
105
+
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: routr
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Levente Bagi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-29 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ description: Implements Dijkstra's algorithm to find the shortest path in a graph. The cost of an edge can be more general than just a numeric value.
35
+ email:
36
+ - levente@picklive.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - .gitignore
45
+ - .rspec
46
+ - .travis.yml
47
+ - Gemfile
48
+ - Guardfile
49
+ - LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - cruise_config.rb
53
+ - lib/routr.rb
54
+ - lib/routr/calculator.rb
55
+ - lib/routr/interface.rb
56
+ - lib/routr/version.rb
57
+ - routr.gemspec
58
+ - spec/integration/routr_spec.rb
59
+ - spec/unit/calculator_spec.rb
60
+ homepage: ""
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options: []
65
+
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.17
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Finds the shortest path in a graph, allowing unusual cost definitions
93
+ test_files:
94
+ - spec/integration/routr_spec.rb
95
+ - spec/unit/calculator_spec.rb