Moby 0.5.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.
@@ -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
+