Moby 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,65 @@
1
+ Context::define :CalculateShortestDistance do
2
+
3
+ role :tentative_distance_values do end
4
+ role :path do end
5
+
6
+ role :current do end
7
+ role :destination do end
8
+
9
+
10
+ role :map do
11
+ distance_between do |a, b|
12
+ map.distances[Edge.new(a, b)]
13
+ end
14
+
15
+ # These two functions presume always travelling
16
+ # in a southern or easterly direction
17
+ next_down_the_street_from do |x|
18
+ map.east_neighbor_of x
19
+ end
20
+
21
+ next_along_the_avenue_from do |x|
22
+ map.south_neighbor_of x
23
+ end
24
+ end
25
+
26
+ role :current do
27
+ tentative_distance do
28
+ tentative_distance_values[current]
29
+ end
30
+ set_tentative_distance_to do |x|
31
+ tentative_distance_values[current] = x
32
+ end
33
+ end
34
+
35
+
36
+ rebind do |origin_node, geometries|
37
+ @current = origin_node
38
+ @destination = geometries.destination
39
+ @map = geometries
40
+ end
41
+
42
+ distance do
43
+ current.set_tentative_distance_to 0
44
+ @path = CalculateShortestPath.new(current, destination, map).path
45
+ retval = 0
46
+ previous_node = nil
47
+ path.reverse_each {|node|
48
+ if previous_node.nil?
49
+ retval = 0
50
+ else
51
+ retval += map.distance_between previous_node, node
52
+ end
53
+ previous_node = node
54
+ }
55
+ retval
56
+ end
57
+
58
+ end
59
+
60
+ class CalculateShortestDistance
61
+ def initialize(origin_node, geometries)
62
+ rebind(origin_node, geometries)
63
+ @tentative_distance_values = Hash.new
64
+ end
65
+ end
@@ -0,0 +1,352 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Consider street corners on a Manhattan grid. We want to find the
4
+ # minimal path from the most northeast city to the most
5
+ # southeast city. Use Dijstra's algorithm
6
+ #
7
+
8
+
9
+ #
10
+ # --------- Contexts: the home of the use cases for the example --------------
11
+ #
12
+
13
+ #
14
+ # ---------- This is the main Context for shortest path calculation -----------
15
+ #
16
+
17
+
18
+
19
+ # There are eight roles in the algorithm:
20
+ #
21
+ # pathTo, which is the interface to whatever accumulates the path
22
+ # current, which is the current intersection in the recursive algorithm
23
+ # east_neighbor, which lies DIRECTLY to the east of current
24
+ # south_neighbor, which is DIRECTLy to its south
25
+ # destination, the target node
26
+ # map, which is the oracle for the geometry
27
+ # tentative_distance_values, which supports the algorithm, and is
28
+ # owned by the CalculateShortestPath context (it is context data)
29
+ #
30
+ #
31
+ # The algorithm is straight from Wikipedia:
32
+ #
33
+ # http://en.wikipedia.org/wiki/Dijkstra's_algorithm
34
+ #
35
+ # and reads directly from the distance method, below
36
+
37
+
38
+ # "Map" as in cartography rather than Computer Science...
39
+ #
40
+ # Map is a DCI role. The role in this example is played by an
41
+ # object representing a particular Manhattan geometry
42
+ Context::define :CalculateShortestPath do
43
+ role :distance_labeled_graph_node do
44
+ # Access to roles and other Context data
45
+ tentative_distance_values do
46
+ tentative_distance_values
47
+ end
48
+ # Role Methods
49
+ tentative_distance do
50
+ tentative_distance_values[@distance_labeled_graph_node]
51
+ end
52
+ set_tentative_distance_to do |x|
53
+ tentative_distance_values[@distance_labeled_graph_node] = x
54
+ end
55
+ end
56
+
57
+ # These are handles to to the roles
58
+ role :map do
59
+ distance_between do |a, b|
60
+ dist = @map.distances[Edge.new(a, b)]
61
+ # p "distance between #{a.name} and #{b.name} is #{dist}"
62
+ dist
63
+ end
64
+ next_down_the_street_from do |x|
65
+ n = east_neighbor_of x
66
+ # p "next down the street from #{x.name} is #{n.name}"
67
+ n
68
+ end
69
+ next_along_the_avenue_from do |x|
70
+ n = south_neighbor_of x
71
+ # p "next along the avenue from #{x.name} is #{n.name}"
72
+ n
73
+ end
74
+ origin do
75
+ map.root
76
+ end
77
+ nearest_unvisited_node_to_target do
78
+ min = infinity
79
+ selection = nil
80
+ @unvisited.each_key {
81
+ |intersection|
82
+ bind :intersection=>:distance_labeled_graph_node
83
+ if @unvisited[intersection]
84
+ tentative_distance = intersection.tentative_distance
85
+ if tentative_distance < min
86
+ # p "min distance is updated from #{min} to #{tentative_distance}"
87
+ min = tentative_distance
88
+ selection = intersection
89
+ end
90
+ end
91
+ }
92
+ selection
93
+ end
94
+ unvisited do
95
+ @unvisited
96
+ end
97
+ end
98
+
99
+ role :current do
100
+ # Access to roles and other Context data
101
+ unvisited do
102
+ map.unvisited
103
+ end
104
+
105
+ # Role Methods
106
+ unvisited_neighbors do
107
+ retval = Array.new
108
+ if @south_neighbor != nil
109
+ if unvisited[@south_neighbor] then retval << @south_neighbor end
110
+ end
111
+ if @east_neighbor != nil
112
+ if unvisited[@east_neighbor] then retval << @east_neighbor end
113
+ end
114
+ # p "unvisited neighbors #{retval}"
115
+ retval
116
+ end
117
+ tentative_distance do
118
+ raise "key (#{current}) not found in #{@tentative_distance_values}" unless @tentative_distance_values && (@tentative_distance_values.has_key? current)
119
+ @tentative_distance_values[current]
120
+ end
121
+ end
122
+ role :unvisited do end
123
+
124
+
125
+ # This module serves to provide the methods both for the
126
+ # east_neighbor and south_neighbor roles
127
+
128
+ role :neighbor_node do
129
+ relable_node_as do |x|
130
+ raise "Argument can't be nil" unless x
131
+ raise "self can't be nil" unless @neighbor_node
132
+
133
+ if x < neighbor_node.tentative_distance
134
+ # p "updated tentative distance from #{neighbor_node.tentative_distance} to #{x}"
135
+ neighbor_node.set_tentative_distance_to x
136
+ :distance_was_udated
137
+ else
138
+ # p "left tentative distance at #{neighbor_node.tentative_distance} instead of #{x}"
139
+ :distance_was_not_udated
140
+ end
141
+ end
142
+
143
+ # Role Methods
144
+ tentative_distance do
145
+ raise "self can't be nil" unless @neighbor_node
146
+ tentative_distance_values[@neighbor_node]
147
+ end
148
+ set_tentative_distance_to do |x|
149
+ raise "Argument can't be nil" unless x
150
+ raise "self can't be nil" unless @neighbor_node
151
+ tentative_distance_values[@neighbor_node] = x
152
+ end
153
+ end
154
+ # This is the method that starts the work. Called from initialize.
155
+
156
+ execute do |path_vector, unvisited_hash, pathto_hash, tentative_distance_values_hash|
157
+ do_inits(path_vector, unvisited_hash, pathto_hash,
158
+ tentative_distance_values_hash)
159
+
160
+
161
+ # Calculate tentative distances of unvisited neighbors
162
+
163
+ unvisited_neighbors = current.unvisited_neighbors
164
+ # p "#{unvisited_neighbors}"
165
+ if unvisited_neighbors != nil
166
+ unvisited_neighbors.each {
167
+ |neighbor|
168
+ bind :neighbor => :neighbor_node
169
+ tentative_distance = current.tentative_distance
170
+ raise "tentative distance can't be nil" if tentative_distance == nil
171
+ distance_between = map.distance_between(current, neighbor)
172
+ raise "distance between can't be nil" if distance_between == nil
173
+ net_distance = tentative_distance + distance_between
174
+
175
+ if neighbor.relable_node_as(net_distance) == :distance_was_udated
176
+ # p "set path"
177
+ pathTo[neighbor] = @current
178
+ # p "path #{@pathTo}"
179
+ end
180
+ }
181
+ end
182
+ unvisited.delete(@current)
183
+
184
+ # Are we done?
185
+ if unvisited.size == 0
186
+ save_path(@path)
187
+ else
188
+ # The next current node is the one with the least distance in the
189
+ # unvisited set
190
+ selection = map.nearest_unvisited_node_to_target
191
+
192
+
193
+ # Recur
194
+ CalculateShortestPath.new(selection, destination, map, path, @unvisited,
195
+ pathTo, tentative_distance_values)
196
+ end
197
+ end
198
+
199
+ do_inits do |path_vector, unvisited_hash, pathto_hash, tentative_distance_values_hash|
200
+
201
+ # The conditional switches between the first and subsequent instances of the
202
+ # recursion (the algorithm is recursive in graph contexts)
203
+ if path_vector.nil?
204
+
205
+ def do_inits(path_vector, unvisited_hash, pathto_hash, tentative_distance_values_hash)
206
+ # The conditional switches between the first and subsequent instances of the
207
+ # recursion (the algorithm is recursive in graph contexts)
208
+ if path_vector.nil?
209
+ # Since path_vector isn't set up, this is the first iteration of the recursion
210
+ @tentative_distance_values = Hash.new
211
+
212
+ # This is the fundamental data structure for Dijkstra's algorithm, called
213
+ # "Q" in the Wikipedia description. It is a boolean hash that maps a
214
+ # node onto false or true according to whether it has been visited
215
+
216
+ @unvisited = Hash.new
217
+
218
+ # These initializations are directly from the description of the algorithm
219
+ map.nodes.each { |node| @unvisited[node] = true }
220
+ @unvisited.delete(map.origin)
221
+ map.nodes.each { |node| bind :node=>:distance_labeled_graph_node; node.set_tentative_distance_to(infinity) }
222
+ tentative_distance_values[map.origin] = 0
223
+
224
+ # The path array is kept in the outermost context and serves to store the
225
+ # return path. Each recurring context may add something to the array along
226
+ # the way. However, because of the nature of the algorithm, individual
227
+ # Context instances don't deliver "partial paths" as partial answers.
228
+ @path = Array.new
229
+
230
+ # The pathTo map is a local associative array that remembers the
231
+ # arrows between nodes through the array and erases them if we
232
+ # re-label a node with a shorter distance
233
+
234
+ @pathTo = Hash.new
235
+
236
+ else
237
+
238
+ # We are recurring. Just copy the values copied in from the previous iteration
239
+ # of the recursion
240
+
241
+ @tentative_distance_values = tentative_distance_values_hash
242
+ @unvisited = unvisited_hash
243
+ @path = path_vector
244
+ @pathTo = pathto_hash
245
+ end
246
+ end
247
+ # Since path_vector isn't set up, this is the first iteration of the recursion
248
+
249
+ @tentative_distance_values = Hash.new
250
+
251
+ # This is the fundamental data structure for Dijkstra's algorithm, called
252
+ # "Q" in the Wikipedia description. It is a boolean hash that maps a
253
+ # node onto false or true according to whether it has been visited
254
+
255
+ @unvisited = Hash.new
256
+
257
+ # These initializations are directly from the description of the algorithm
258
+ map.nodes.each { |node| @unvisited[node] = true }
259
+ @unvisited.delete(map.origin)
260
+ # p "map #{map.nodes}"
261
+ map.nodes.each { |node|
262
+ bind :node => :distance_labeled_graph_node;
263
+ node.set_tentative_distance_to(infinity)
264
+ # p "initialized node #{node.name}"
265
+ }
266
+ tentative_distance_values[map.origin] = 0
267
+
268
+
269
+ # The path array is kept in the outermost context and serves to store the
270
+ # return path. Each recurring context may add something to the array along
271
+ # the way. However, because of the nature of the algorithm, individual
272
+ # Context instances don't deliver "partial paths" as partial answers.
273
+
274
+ @path = Array.new
275
+
276
+ # The pathTo map is a local associative array that remembers the
277
+ # arrows between nodes through the array and erases them if we
278
+ # re-label a node with a shorter distance
279
+
280
+ @pathTo = Hash.new
281
+
282
+ else
283
+
284
+ # We are recurring. Just copy the values copied in from the previous iteration
285
+ # of the recursion
286
+
287
+ @tentative_distance_values = tentative_distance_values_hash
288
+ @unvisited = unvisited_hash
289
+ @path = path_vector
290
+ @pathTo = pathto_hash
291
+ end
292
+ end
293
+ end
294
+
295
+ class CalculateShortestPath
296
+
297
+ def pathTo
298
+ @pathTo
299
+ end
300
+ def east_neighbor; @east_neighbor end
301
+ def south_neighbor; @south_neighbor end
302
+ def path; @path end
303
+
304
+ def destination; @destination end
305
+ def tentative_distance_values; @tentative_distance_values end
306
+
307
+ # This is a shortcut to information that really belongs in the Map.
308
+ # To keep roles stateless, we hold the Map's unvisited structure in the
309
+ # Context object. We access it as though it were in the map
310
+
311
+
312
+ # Initialization
313
+ def rebind(origin_node, geometries)
314
+ @current = origin_node
315
+ @map = geometries
316
+
317
+ @east_neighbor = map.east_neighbor_of(origin_node)
318
+ @south_neighbor = map.south_neighbor_of(origin_node)
319
+ end
320
+
321
+
322
+ # public initialize. It's overloaded so that the public version doesn't
323
+ # have to pass a lot of crap; the initialize method takes care of
324
+ # setting up internal data structures on the first invocation. On
325
+ # recursion we override the defaults
326
+
327
+ def initialize(origin_node, target_node, geometries,
328
+ path_vector = nil, unvisited_hash = nil, pathto_hash = nil,
329
+ tentative_distance_values_hash = nil)
330
+ @destination = target_node
331
+
332
+ rebind(origin_node, geometries)
333
+
334
+ execute(path_vector, unvisited_hash, pathto_hash, tentative_distance_values_hash)
335
+ end
336
+ def each
337
+ path.each { |node| yield node }
338
+ end
339
+
340
+
341
+ # This method does a simple traversal of the data structures (following pathTo)
342
+ # to build the directed traversal vector for the minimum path
343
+
344
+ def save_path(pathVector)
345
+ node = destination
346
+ begin
347
+ pathVector << node
348
+ node = pathTo[node]
349
+ end while node != nil
350
+ end
351
+ end
352
+
@@ -0,0 +1,210 @@
1
+ def infinity; (2**(0.size * 8 -2) -1) end
2
+ # Data classes
3
+ Edge = Struct.new(:from, :to)
4
+
5
+ class Node
6
+ attr_reader :name
7
+ def initialize(n); @name = n end
8
+ def eql? (another_node)
9
+ # Nodes are == equal if they have the same name. This is explicitly
10
+ # defined here to call out the importance of the differnce between
11
+ # object equality and identity
12
+ name == another_node.name
13
+ end
14
+ end
15
+
16
+
17
+
18
+ #
19
+ # --- Geometry is the interface to the data class that has all
20
+ # --- the information about the map. This is kind of silly in Ruby
21
+ #
22
+ class ManhattanGeometry
23
+ def initialize;
24
+ @nodes = Array.new
25
+ @distances = Hash.new
26
+ end
27
+
28
+ def nodes; @nodes end
29
+ def distances
30
+ @distances
31
+ end
32
+ end
33
+
34
+
35
+
36
+ #
37
+ # --- Here are some test data
38
+ #
39
+
40
+ #noinspection RubyTooManyInstanceVariablesInspection
41
+ class Geometry_1 < ManhattanGeometry
42
+ def initialize
43
+ super()
44
+
45
+ names = %w('a' 'b' 'c' 'd' 'a' 'b' 'g' 'h' 'i')
46
+
47
+ 3.times { |i|
48
+ 3.times { |j|
49
+ nodes << Node.new(names[(i*3)+j])
50
+ }
51
+ }
52
+
53
+
54
+
55
+ # Aliases to help set up the grid. Grid is of Manhattan form:
56
+ #
57
+ # a - 2 - b - 3 - c
58
+ # | | |
59
+ # 1 2 1
60
+ # | | |
61
+ # d - 1 - e - 1 - f
62
+ # | |
63
+ # 2 4
64
+ # | |
65
+ # g - 1 - h - 2 - i
66
+ #
67
+
68
+
69
+ @node_a = nodes[0]
70
+ @node_b = nodes[1]
71
+ @node_c = nodes[2]
72
+ @node_d = nodes[3]
73
+ @node_e = nodes[4]
74
+ @node_f = nodes[5]
75
+ @node_g = nodes[6]
76
+ @node_h = nodes[7]
77
+ @node_i = nodes[8]
78
+
79
+ 9.times { |i|
80
+ 9.times { |j|
81
+ distances[Edge.new(nodes[i], nodes[j])] = infinity
82
+ }
83
+ }
84
+
85
+ distances[Edge.new(@node_a, @node_b)] = 2
86
+ distances[Edge.new(@node_b, @node_c)] = 3
87
+ distances[Edge.new(@node_c, @node_f)] = 1
88
+ distances[Edge.new(@node_f, @node_i)] = 4
89
+ distances[Edge.new(@node_b, @node_e)] = 2
90
+ distances[Edge.new(@node_e, @node_f)] = 1
91
+ distances[Edge.new(@node_a, @node_d)] = 1
92
+ distances[Edge.new(@node_d, @node_g)] = 2
93
+ distances[Edge.new(@node_g, @node_h)] = 1
94
+ distances[Edge.new(@node_h, @node_i)] = 2
95
+ distances[Edge.new(@node_d, @node_e)] = 1
96
+ distances.freeze
97
+
98
+
99
+ @next_down_the_street_from = Hash.new
100
+ @next_down_the_street_from[@node_a] = @node_b
101
+ @next_down_the_street_from[@node_b] = @node_c
102
+ @next_down_the_street_from[@node_d] = @node_e
103
+ @next_down_the_street_from[@node_e] = @node_f
104
+ @next_down_the_street_from[@node_g] = @node_h
105
+ @next_down_the_street_from[@node_h] = @node_i
106
+ @next_down_the_street_from.freeze
107
+
108
+ @next_along_the_avenue_from = Hash.new
109
+ @next_along_the_avenue_from[@node_a] = @node_d
110
+ @next_along_the_avenue_from[@node_b] = @node_e
111
+ @next_along_the_avenue_from[@node_c] = @node_f
112
+ @next_along_the_avenue_from[@node_d] = @node_g
113
+ @next_along_the_avenue_from[@node_f] = @node_i
114
+ @next_along_the_avenue_from.freeze
115
+ end
116
+
117
+ def east_neighbor_of(a); @next_down_the_street_from[a] end
118
+ def south_neighbor_of(a); @next_along_the_avenue_from[a] end
119
+
120
+ def root; @node_a end
121
+ def destination; @node_i end
122
+ end
123
+
124
+
125
+ #noinspection RubyTooManyInstanceVariablesInspection
126
+ class ManhattanGeometry2 < ManhattanGeometry
127
+ def initialize
128
+ super()
129
+ names = %w('a' 'b' 'c' 'd' 'a' 'b' 'g' 'h' 'i' 'j' 'k')
130
+
131
+ 11.times { |j| nodes << Node.new(names[j]) }
132
+
133
+
134
+ # Aliases to help set up the grid. Grid is of Manhattan form:
135
+ #
136
+ # a - 2 - b - 3 - c - 1 - j
137
+ # | | | |
138
+ # 1 2 1 |
139
+ # | | | |
140
+ # d - 1 - e - 1 - f 1
141
+ # | | |
142
+ # 2 4 |
143
+ # | | |
144
+ # g - 1 - h - 2 - i - 2 - k
145
+
146
+
147
+ #
148
+ @node_a = nodes[0]
149
+ @node_b = nodes[1]
150
+ @node_c = nodes[2]
151
+ @node_d = nodes[3]
152
+ @node_e = nodes[4]
153
+ @node_f = nodes[5]
154
+ @node_g = nodes[6]
155
+ @node_h = nodes[7]
156
+ @node_i = nodes[8]
157
+ @node_j = nodes[9]
158
+ @node_k = nodes[10]
159
+
160
+ 11.times { |i|
161
+ 11.times { |j|
162
+ distances[Edge.new(nodes[i], nodes[j])] = infinity
163
+ }
164
+ }
165
+
166
+ distances[Edge.new(@node_a, @node_b)] = 2
167
+ distances[Edge.new(@node_b, @node_c)] = 3
168
+ distances[Edge.new(@node_c, @node_f)] = 1
169
+ distances[Edge.new(@node_f, @node_i)] = 4
170
+ distances[Edge.new(@node_b, @node_e)] = 2
171
+ distances[Edge.new(@node_e, @node_f)] = 1
172
+ distances[Edge.new(@node_a, @node_d)] = 1
173
+ distances[Edge.new(@node_d, @node_g)] = 2
174
+ distances[Edge.new(@node_g, @node_h)] = 1
175
+ distances[Edge.new(@node_h, @node_i)] = 2
176
+ distances[Edge.new(@node_d, @node_e)] = 1
177
+ distances[Edge.new(@node_c, @node_j)] = 1
178
+ distances[Edge.new(@node_j, @node_k)] = 1
179
+ distances[Edge.new(@node_i, @node_k)] = 2
180
+ distances.freeze
181
+
182
+
183
+ @next_down_the_street_from = Hash.new
184
+ @next_down_the_street_from[@node_a] = @node_b
185
+ @next_down_the_street_from[@node_b] = @node_c
186
+ @next_down_the_street_from[@node_c] = @node_j
187
+ @next_down_the_street_from[@node_d] = @node_e
188
+ @next_down_the_street_from[@node_e] = @node_f
189
+ @next_down_the_street_from[@node_g] = @node_h
190
+ @next_down_the_street_from[@node_h] = @node_i
191
+ @next_down_the_street_from[@node_i] = @node_k
192
+ @next_down_the_street_from.freeze
193
+
194
+ @next_along_the_avenue_from = Hash.new
195
+ @next_along_the_avenue_from[@node_a] = @node_d
196
+ @next_along_the_avenue_from[@node_b] = @node_e
197
+ @next_along_the_avenue_from[@node_c] = @node_f
198
+ @next_along_the_avenue_from[@node_d] = @node_g
199
+ @next_along_the_avenue_from[@node_f] = @node_i
200
+ @next_along_the_avenue_from[@node_j] = @node_k
201
+ @next_along_the_avenue_from.freeze
202
+ end
203
+
204
+ def east_neighbor_of(a); @next_down_the_street_from[a] end
205
+ def south_neighbor_of(a); @next_along_the_avenue_from[a] end
206
+
207
+ def root; @node_a end
208
+ def destination; @node_k end
209
+ end
210
+