ruby-graph-walker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 098a0fa35d5773837d9b335237aa86d664835da3
4
+ data.tar.gz: 2450e77e9c9de04c866daeaad447f81f34f2d58b
5
+ SHA512:
6
+ metadata.gz: 43d80e28ce884734f03b408099f8ee123e558ed631300d36f4342e5443606f45b9e94c9d22d8af579a3724313d453a55f7f2614e8e5c79081f942e4188b273a5
7
+ data.tar.gz: ab81246bc8d2c9f36f3aa49e598fe4086e33d89c604f29e3e1f49dee49b1528e346ef959c8f8004cb90bd7362c0191eca3ee76a2bdcb04466e9fbf0d6f1cbb3c
data/lib/colorize.rb ADDED
@@ -0,0 +1,28 @@
1
+ module RubyGraphWalker
2
+ def colorize(text, color_code)
3
+ "\e[#{color_code}m#{text}\e[0m"
4
+ end
5
+
6
+ def red(text); colorize(text, 31); end
7
+ def green(text); colorize(text, 32); end
8
+ def yellow(text); colorize(text, 33); end
9
+ def blue(text); colorize(text, 34); end
10
+ def magenta(text); colorize(text, 35); end
11
+ def cyan(text); colorize(text, 36); end
12
+ def yellow_background(text); colorize(text, 43); end
13
+ def magenta_background(text); colorize(text, 45); end
14
+ def exchange_foreground_and_background(text); colorize(text, 7); end
15
+ def light_red(text); colorize(text, 91); end
16
+
17
+ def log_warning(msg)
18
+ puts magenta(msg)
19
+ end
20
+
21
+ def log_error(msg)
22
+ puts light_red(msg)
23
+ end
24
+
25
+ def log_info(msg)
26
+ puts green(msg)
27
+ end
28
+ end
data/lib/dijkstra.rb ADDED
@@ -0,0 +1,57 @@
1
+ require_relative 'pq'
2
+
3
+ class Dijkstra
4
+ def initialize(graph, source_vertex)
5
+ @graph = graph
6
+ @vertices = @graph.vertices
7
+ @source_vertex = source_vertex
8
+ @path_to = {}
9
+ @distance_to = {}
10
+ @pq = PriorityQueue.new
11
+ compute_shortest_path
12
+ end
13
+
14
+ def shortest_path_to(vertex)
15
+ return [] unless reachable?(vertex)
16
+ return [] if @distance_to[vertex] == 0
17
+ path = []
18
+ while vertex != @source_vertex
19
+ path.unshift(vertex)
20
+ vertex = @path_to[vertex]
21
+ end
22
+ path.unshift(@source_vertex)
23
+ end
24
+
25
+ private
26
+
27
+ def reachable?(vertex)
28
+ not @distance_to[vertex] == Float::INFINITY
29
+ end
30
+
31
+ def compute_shortest_path
32
+ update_distance_of_all_edges_to(Float::INFINITY)
33
+ @distance_to[@source_vertex] = 0
34
+
35
+ @pq.insert(@source_vertex, 0)
36
+ while @pq.any?
37
+ vertex = @pq.remove_min
38
+ @vertices[vertex].edges.each do |edge|
39
+ update(edge)
40
+ end
41
+ end
42
+ end
43
+
44
+ def update_distance_of_all_edges_to(distance)
45
+ @vertices.each do |key, value|
46
+ @distance_to[key] = distance
47
+ end
48
+ end
49
+
50
+ def update(edge)
51
+ raise "#{edge.to} doesn't exist, check edge[:to] in #{edge.inspect}" if @distance_to[edge.to].nil?
52
+ return if @distance_to[edge.to] <= @distance_to[edge.from] + edge.weight
53
+ @distance_to[edge.to] = @distance_to[edge.from] + edge.weight
54
+ @path_to[edge.to] = edge.from
55
+ @pq.insert(edge.to, @distance_to[edge.to])
56
+ end
57
+ end
data/lib/graph.rb ADDED
@@ -0,0 +1,153 @@
1
+ require 'dijkstra'
2
+ require 'colorize'
3
+
4
+ module RubyGraphWalker
5
+ class Vertex
6
+ attr_accessor :name, :weight, :edges, :trait, :visited, :zindex
7
+ def initialize(args = {})
8
+ [:name, :edges, :trait].each { |key| raise "#{key} is not defined for Vertex #{args}" unless args[key] }
9
+ @name = args[:name]
10
+ @weight = args[:weight] || 1
11
+ @trait = args[:trait]
12
+ @visited = args[:visited] || false
13
+ @weight = args[:zindex] || 0
14
+ @edges = []
15
+
16
+ args[:edges].each do |edge|
17
+ e = Edge.new(edge)
18
+ e.from = @name
19
+ e.to = edge[:to]
20
+ add_edge(e)
21
+ end
22
+ end
23
+
24
+ private
25
+ def add_edge(e)
26
+ @edges << e
27
+ end
28
+ end
29
+
30
+ class Edge
31
+ attr_accessor :name, :from, :to, :weight, :visited, :proc, :error_count
32
+ def initialize(args = {})
33
+ [:name, :to, :proc].each { |key| raise "#{key} is not defined for Edge #{args}" unless args[key] }
34
+ @name = args[:name]
35
+ @from = args[:from]
36
+ @to = args[:to]
37
+ @weight = args[:weight] || 1
38
+ @visited = args[:visited] || false
39
+ @proc = args[:proc]
40
+ @error_count = 0
41
+ end
42
+
43
+ def run
44
+ @proc.call
45
+ @visited = true
46
+ end
47
+ end
48
+
49
+ class Graph
50
+ attr_accessor :vertices, :edges, :edges_by_name
51
+ def initialize(vertices)
52
+ @vertices = {}
53
+ @edges_by_name = {}
54
+ @edges = []
55
+ vertices.each do |v_params|
56
+ v = Vertex.new(v_params)
57
+ @vertices[v_params[:name]] = v
58
+ v.edges.each do |edge|
59
+ puts "Warning: multiple edges named '#{edge.name}'" if @edges_by_name[edge.name]
60
+ @edges_by_name[edge.name] = edge
61
+ @edges << edge
62
+ end
63
+ end
64
+ end
65
+
66
+ def find_path_via_edge(from, to, edge_name)
67
+ log_info "#{from} -> #{to} via: (#{edge_name}): "
68
+ matched_edges = @edges.select { |edge| edge.name == edge_name }
69
+ log_error "no edge found for '#{edge_name}'" if matched_edges.size == 0
70
+ log_error "multiple edges matched for '#{edge_name}'" if matched_edges.size > 1
71
+ via_edge = matched_edges.first
72
+ plan = []
73
+
74
+ first_path = Dijkstra.new(self, from).shortest_path_to(via_edge.from)
75
+
76
+ first_path.each_cons(2) do |path|
77
+ start, dest = path
78
+ vertex = @vertices[start]
79
+ edge = vertex.edges.select { |e| e.to == dest}.first
80
+ plan << {v: vertex, e: edge}
81
+ end
82
+
83
+ via_v = @vertices[via_edge.from]
84
+ plan << {v: via_v, e: via_edge}
85
+
86
+ second_path = Dijkstra.new(self, via_edge.to).shortest_path_to(to)
87
+
88
+ second_path.each_cons(2) do |path|
89
+ start, dest = path
90
+ vertex = @vertices[start]
91
+ edge = vertex.edges.select { |e| e.to == dest }.first
92
+ plan << {v: vertex, e: edge}
93
+ end
94
+ plan
95
+ end
96
+
97
+ def find_path(from, to, args = {})
98
+ vertex_path = []
99
+ plan = []
100
+ via_type = args.keys.join
101
+
102
+ case via_type
103
+ when "via"
104
+ via = args[:via]
105
+ vertex_path = Dijkstra.new(self, from).shortest_path_to(via) + Dijkstra.new(self, via).shortest_path_to(to).drop(1)
106
+ vertex_path.each_cons(2) do |path|
107
+ f, d = path
108
+ vertex = @vertices[d]
109
+ edge = vertex.edges.select { |e| e.to == d}.first
110
+ plan << {v: vertex, e: edge}
111
+ end
112
+ when "via_edge"
113
+ via_edge = args[:via_edge]
114
+ plan = find_path_via_edge(from, to, via_edge)
115
+
116
+ # when "via_edges"
117
+ # edges = args[:via_edges]
118
+ # if edges.size < 2
119
+ # raise "please specify multiple edges"
120
+ # end
121
+ # v = nil
122
+ # edges[0..-2].each do |edge|
123
+ # start = (plan.last[:v].name if plan.last) || from
124
+ # v = @edges_by_name[edge].to
125
+ # plan += find_path_via_edge(start, v, edge)
126
+ # end
127
+ # plan += find_path_via_edge(v, to, edges.last)
128
+ when ""
129
+ vertex_path = Dijkstra.new(self, from).shortest_path_to(to)
130
+
131
+ vertex_path.each_cons(2) do |path|
132
+ from, d = path
133
+ vertex = @vertices[from]
134
+ edge = vertex.edges.select { |e| e.to == d}.first
135
+ plan << {v: vertex, e: edge}
136
+ end
137
+ if plan.empty? and from == to
138
+ return :on_the_spot
139
+ end
140
+ end
141
+
142
+ if plan.any?
143
+ puts plan.map {|p| "#{p[:v].name if p[:v]} (#{p[:e].name if p[:e]})" }.join(" -> ") + " >> #{@vertices[to].name}"
144
+ else
145
+ raise "No path from #{from} to #{to}"
146
+ end
147
+ plan
148
+ end
149
+ end
150
+
151
+ end
152
+
153
+
data/lib/pq.rb ADDED
@@ -0,0 +1,23 @@
1
+ class PriorityQueue
2
+ def initialize
3
+ @queue = {}
4
+ end
5
+
6
+ def any?
7
+ @queue.any?
8
+ end
9
+
10
+ def insert(key, value)
11
+ @queue[key] = value
12
+ order_queue
13
+ end
14
+
15
+ def remove_min
16
+ @queue.shift.first
17
+ end
18
+
19
+ private
20
+ def order_queue
21
+ @queue.sort_by {|_key, value| value }
22
+ end
23
+ end
@@ -0,0 +1,2 @@
1
+ require 'graph'
2
+ require 'test_planner'
@@ -0,0 +1,98 @@
1
+ require 'colorize'
2
+
3
+ module RubyGraphWalker
4
+ module TestPlanner
5
+ def search_vertex(query_method = 'query')
6
+ vertices = @graph.vertices.values
7
+ candidates = vertices.select do |v|
8
+ trait = v.trait
9
+ if trait.is_a?(Proc)
10
+ trait.call
11
+ elsif trait.is_a?(Array)
12
+ trait.any? { |t| (send query_method, t).any? }
13
+ else
14
+ send(query_method, trait).any?
15
+ end
16
+ end
17
+
18
+ if candidates.size > 1
19
+ log_warning "Warning: multiple vertices found: #{candidates.map {|c| c[:name]}.join(' ') }"
20
+ elsif candidates.size == 0
21
+ if send(query_method, "*").any?
22
+ raise "No vertex found"
23
+ else
24
+ raise "Connection refused"
25
+ end
26
+ end
27
+ candidates.max_by {|v| v.zindex}
28
+ end
29
+
30
+ def path_to(to, args = {})
31
+ v = search_vertex
32
+ @graph.find_path(v.name, to, args)
33
+ end
34
+
35
+ def run(plan, args = {})
36
+ raise "on the spot" if plan == :on_the_spot
37
+
38
+ plan.each do |p|
39
+ edge = p[:e]
40
+ raise "edge is nil" if edge.nil?
41
+ edge.run
42
+ @graph.vertices[edge.from].visited = true
43
+ end
44
+ end
45
+
46
+ def text_logger(args = {})
47
+ log_file_name = "#{Time.now.strftime('%Y_%m_%d_%H_%M_%S')}.txt"
48
+ @log_file ||= File.open(log_file_name, 'w')
49
+ end
50
+
51
+ def log(msg)
52
+ text_logger.puts(msg)
53
+ end
54
+
55
+ def start(graph, args = {})
56
+ @graph = graph
57
+ unvisited_edges = @graph.edges_by_name.select { |k, v| v.visited == false and v.weight > 1 }
58
+ starting_point = args[:start] || @graph.vertices.keys.first
59
+ while unvisited_edges.any?
60
+ edge_name = largest_weight(unvisited_edges).first
61
+ edge = @graph.edges_by_name[edge_name]
62
+ plan = path_to(starting_point, via_edge: edge_name)
63
+ begin
64
+ run(plan)
65
+ rescue => e
66
+ edge.error_count += 1
67
+ log edge.name + " failed! #{edge.error_count}"
68
+ e.backtrace.each do |text|
69
+ log text
70
+ end
71
+
72
+ retries = 3
73
+ begin
74
+ sleep 10
75
+ new_plan = path_to(edge.from)
76
+ run(new_plan, args)
77
+ rescue
78
+ edge.error_count += 1
79
+ retry unless (retries -= 1).zero?
80
+ end
81
+ end
82
+ unvisited_edges = @graph.edges_by_name.select { |k, v| v.visited == false and v.weight > 1 and v.error_count < 3}
83
+ end
84
+
85
+ @graph.edges_by_name.each do |k, v|
86
+ log "#{k} error count: #{v.error_count}"
87
+ end
88
+
89
+ text_logger.close
90
+ end
91
+
92
+ private
93
+ def largest_weight(hash)
94
+ hash.max_by{|k,v| v.weight}
95
+ end
96
+
97
+ end
98
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-graph-walker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - astro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-09 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email: astro2linus@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/colorize.rb
20
+ - lib/dijkstra.rb
21
+ - lib/graph.rb
22
+ - lib/pq.rb
23
+ - lib/ruby_graph_walker.rb
24
+ - lib/test_planner.rb
25
+ homepage: ''
26
+ licenses:
27
+ - MIT
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.4.8
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Ruby Graph Walker
49
+ test_files: []