routr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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