rgl 0.2.2
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.
- data/ChangeLog +74 -0
- data/Makefile +72 -0
- data/README +240 -0
- data/Rakefile +210 -0
- data/TAGS +209 -0
- data/examples/canvas.rb +103 -0
- data/examples/codegraph +238 -0
- data/examples/example.jpg +0 -0
- data/examples/examples.rb +112 -0
- data/examples/graph.dot +54 -0
- data/examples/graph.png +0 -0
- data/examples/module_graph.jpg +0 -0
- data/examples/north.rb +12 -0
- data/examples/north/Graph.log +128 -0
- data/examples/north/g.10.0.graphml +28 -0
- data/examples/north/g.10.1.graphml +28 -0
- data/examples/north/g.10.11.graphml +31 -0
- data/examples/north/g.10.12.graphml +27 -0
- data/examples/north/g.10.13.graphml +27 -0
- data/examples/north/g.10.14.graphml +27 -0
- data/examples/north/g.10.15.graphml +26 -0
- data/examples/north/g.10.16.graphml +26 -0
- data/examples/north/g.10.17.graphml +26 -0
- data/examples/north/g.10.19.graphml +37 -0
- data/examples/north/g.10.2.graphml +28 -0
- data/examples/north/g.10.20.graphml +38 -0
- data/examples/north/g.10.22.graphml +43 -0
- data/examples/north/g.10.24.graphml +30 -0
- data/examples/north/g.10.25.graphml +45 -0
- data/examples/north/g.10.27.graphml +38 -0
- data/examples/north/g.10.28.graphml +30 -0
- data/examples/north/g.10.29.graphml +38 -0
- data/examples/north/g.10.3.graphml +26 -0
- data/examples/north/g.10.30.graphml +34 -0
- data/examples/north/g.10.31.graphml +42 -0
- data/examples/north/g.10.34.graphml +42 -0
- data/examples/north/g.10.37.graphml +28 -0
- data/examples/north/g.10.38.graphml +38 -0
- data/examples/north/g.10.39.graphml +36 -0
- data/examples/north/g.10.4.graphml +26 -0
- data/examples/north/g.10.40.graphml +37 -0
- data/examples/north/g.10.41.graphml +37 -0
- data/examples/north/g.10.42.graphml +26 -0
- data/examples/north/g.10.45.graphml +28 -0
- data/examples/north/g.10.46.graphml +32 -0
- data/examples/north/g.10.5.graphml +31 -0
- data/examples/north/g.10.50.graphml +30 -0
- data/examples/north/g.10.56.graphml +29 -0
- data/examples/north/g.10.57.graphml +32 -0
- data/examples/north/g.10.58.graphml +32 -0
- data/examples/north/g.10.6.graphml +26 -0
- data/examples/north/g.10.60.graphml +32 -0
- data/examples/north/g.10.61.graphml +34 -0
- data/examples/north/g.10.62.graphml +34 -0
- data/examples/north/g.10.68.graphml +30 -0
- data/examples/north/g.10.69.graphml +32 -0
- data/examples/north/g.10.7.graphml +29 -0
- data/examples/north/g.10.70.graphml +26 -0
- data/examples/north/g.10.71.graphml +27 -0
- data/examples/north/g.10.72.graphml +28 -0
- data/examples/north/g.10.74.graphml +29 -0
- data/examples/north/g.10.75.graphml +29 -0
- data/examples/north/g.10.78.graphml +27 -0
- data/examples/north/g.10.79.graphml +34 -0
- data/examples/north/g.10.8.graphml +29 -0
- data/examples/north/g.10.80.graphml +34 -0
- data/examples/north/g.10.82.graphml +35 -0
- data/examples/north/g.10.83.graphml +32 -0
- data/examples/north/g.10.85.graphml +34 -0
- data/examples/north/g.10.86.graphml +34 -0
- data/examples/north/g.10.88.graphml +37 -0
- data/examples/north/g.10.89.graphml +29 -0
- data/examples/north/g.10.9.graphml +26 -0
- data/examples/north/g.10.90.graphml +32 -0
- data/examples/north/g.10.91.graphml +31 -0
- data/examples/north/g.10.92.graphml +26 -0
- data/examples/north/g.10.93.graphml +32 -0
- data/examples/north/g.10.94.graphml +34 -0
- data/examples/north/g.12.8.graphml +40 -0
- data/examples/north/g.14.9.graphml +36 -0
- data/examples/north2.rb +21 -0
- data/examples/rdep-rgl.rb +395 -0
- data/install.rb +49 -0
- data/lib/rgl/adjacency.rb +151 -0
- data/lib/rgl/base.rb +299 -0
- data/lib/rgl/connected_components.rb +125 -0
- data/lib/rgl/dot.rb +63 -0
- data/lib/rgl/graphxml.rb +52 -0
- data/lib/rgl/implicit.rb +151 -0
- data/lib/rgl/mutable.rb +54 -0
- data/lib/rgl/rdot.rb +264 -0
- data/lib/rgl/topsort.rb +61 -0
- data/lib/rgl/transitiv_closure.rb +34 -0
- data/lib/rgl/traversal.rb +296 -0
- data/tests/TestComponents.rb +67 -0
- data/tests/TestDirectedGraph.rb +100 -0
- data/tests/TestEdge.rb +33 -0
- data/tests/TestGraphXML.rb +57 -0
- data/tests/TestImplicit.rb +52 -0
- data/tests/TestTransitiveClosure.rb +29 -0
- data/tests/TestTraversal.rb +222 -0
- data/tests/TestUnDirectedGraph.rb +98 -0
- metadata +163 -0
data/lib/rgl/rdot.rb
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#
|
|
2
|
+
# $Id: rdot.rb,v 1.2 2002/11/13 21:53:26 monora Exp $
|
|
3
|
+
#
|
|
4
|
+
# This is a modified version of dot.rb from Dave Thomas rdoc project. I renamed
|
|
5
|
+
# it to rdot.rb to avoid collision with an installed rdoc/dot.
|
|
6
|
+
#
|
|
7
|
+
# It also supports undirected edges.
|
|
8
|
+
|
|
9
|
+
module DOT
|
|
10
|
+
|
|
11
|
+
# these glogal vars are used to make nice graph source
|
|
12
|
+
$tab = ' '
|
|
13
|
+
$tab2 = $tab * 2
|
|
14
|
+
|
|
15
|
+
# if we don't like 4 spaces, we can change it any time
|
|
16
|
+
def change_tab( t )
|
|
17
|
+
$tab = t
|
|
18
|
+
$tab2 = t * 2
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# options for node declaration
|
|
22
|
+
NODE_OPTS = [
|
|
23
|
+
'bgcolor',
|
|
24
|
+
'color',
|
|
25
|
+
'fontcolor',
|
|
26
|
+
'fontname',
|
|
27
|
+
'fontsize',
|
|
28
|
+
'heigth',
|
|
29
|
+
'width',
|
|
30
|
+
'label',
|
|
31
|
+
'layer',
|
|
32
|
+
'rank',
|
|
33
|
+
'shape',
|
|
34
|
+
'shapefile',
|
|
35
|
+
'style',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# options for edge declaration
|
|
39
|
+
EDGE_OPTS = [
|
|
40
|
+
'color',
|
|
41
|
+
'decorate',
|
|
42
|
+
'dir',
|
|
43
|
+
'fontcolor',
|
|
44
|
+
'fontname',
|
|
45
|
+
'fontsize',
|
|
46
|
+
'id',
|
|
47
|
+
'label',
|
|
48
|
+
'layer',
|
|
49
|
+
'minlen',
|
|
50
|
+
'style',
|
|
51
|
+
'weight'
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# options for graph declaration
|
|
55
|
+
GRAPH_OPTS = [
|
|
56
|
+
'bgcolor',
|
|
57
|
+
'center',
|
|
58
|
+
'clusterrank',
|
|
59
|
+
'color',
|
|
60
|
+
'concentrate',
|
|
61
|
+
'fontcolor',
|
|
62
|
+
'fontname',
|
|
63
|
+
'fontsize',
|
|
64
|
+
'label',
|
|
65
|
+
'layerseq',
|
|
66
|
+
'margin',
|
|
67
|
+
'mclimit',
|
|
68
|
+
'nodesep',
|
|
69
|
+
'nslimit',
|
|
70
|
+
'ordering',
|
|
71
|
+
'orientation',
|
|
72
|
+
'page',
|
|
73
|
+
'rank',
|
|
74
|
+
'rankdir',
|
|
75
|
+
'ranksep',
|
|
76
|
+
'ratio',
|
|
77
|
+
'size'
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# a root class for any element in dot notation
|
|
81
|
+
class DOTSimpleElement
|
|
82
|
+
attr_accessor :name
|
|
83
|
+
|
|
84
|
+
def initialize( params = {} )
|
|
85
|
+
@label = params['name'] ? params['name'] : ''
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_s
|
|
89
|
+
@name
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# an element that have options ( node, edge or graph )
|
|
94
|
+
class DOTElement < DOTSimpleElement
|
|
95
|
+
#attr_reader :parent
|
|
96
|
+
attr_accessor :name, :options
|
|
97
|
+
|
|
98
|
+
def initialize( params = {}, option_list = [] )
|
|
99
|
+
super( params )
|
|
100
|
+
@name = params['name'] ? params['name'] : nil
|
|
101
|
+
@parent = params['parent'] ? params['parent'] : nil
|
|
102
|
+
@options = {}
|
|
103
|
+
option_list.each{ |i|
|
|
104
|
+
@options[i] = params[i] if params[i]
|
|
105
|
+
}
|
|
106
|
+
@options['label'] ||= @name if @name != 'node'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def each_option
|
|
110
|
+
@options.each{ |i| yield i }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def each_option_pair
|
|
114
|
+
@options.each_pair{ |key, val| yield key, val }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
#def parent=( thing )
|
|
118
|
+
# @parent.delete( self ) if defined?( @parent ) and @parent
|
|
119
|
+
# @parent = thing
|
|
120
|
+
#end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# this is used when we build nodes that have shape=record
|
|
125
|
+
# ports don't have options :)
|
|
126
|
+
class DOTPort < DOTSimpleElement
|
|
127
|
+
attr_accessor :label
|
|
128
|
+
|
|
129
|
+
def initialize( params = {} )
|
|
130
|
+
super( params )
|
|
131
|
+
@name = params['label'] ? params['label'] : ''
|
|
132
|
+
end
|
|
133
|
+
def to_s
|
|
134
|
+
( @name && @name != "" ? "<#{@name}>" : "" ) + "#{@label}"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# node element
|
|
139
|
+
class DOTNode < DOTElement
|
|
140
|
+
@ports
|
|
141
|
+
|
|
142
|
+
def initialize( params = {}, option_list = NODE_OPTS )
|
|
143
|
+
super( params, option_list )
|
|
144
|
+
@ports = params['ports'] ? params['ports'] : []
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def each_port
|
|
148
|
+
@ports.each{ |i| yield i }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def << ( thing )
|
|
152
|
+
@ports << thing
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def push ( thing )
|
|
156
|
+
@ports.push( thing )
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def pop
|
|
160
|
+
@ports.pop
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def to_s( t = '' )
|
|
164
|
+
|
|
165
|
+
label = @options['shape'] != 'record' && @ports.length == 0 ?
|
|
166
|
+
@options['label'] ?
|
|
167
|
+
t + $tab + "label = \"#{@options['label']}\"\n" :
|
|
168
|
+
'' :
|
|
169
|
+
t + $tab + 'label = "' + " \\\n" +
|
|
170
|
+
t + $tab2 + "#{@options['label']}| \\\n" +
|
|
171
|
+
@ports.collect{ |i|
|
|
172
|
+
t + $tab2 + i.to_s
|
|
173
|
+
}.join( "| \\\n" ) + " \\\n" +
|
|
174
|
+
t + $tab + '"' + "\n"
|
|
175
|
+
|
|
176
|
+
t + "#{@name} [\n" +
|
|
177
|
+
@options.to_a.collect{ |i|
|
|
178
|
+
i[1] && i[0] != 'label' ?
|
|
179
|
+
t + $tab + "#{i[0]} = #{i[1]}" : nil
|
|
180
|
+
}.compact.join( ",\n" ) + ( label != '' ? ",\n" : "\n" ) +
|
|
181
|
+
label +
|
|
182
|
+
t + "]\n"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# subgraph element is the same to graph, but has another header in dot
|
|
187
|
+
# notation
|
|
188
|
+
class DOTSubgraph < DOTElement
|
|
189
|
+
@nodes
|
|
190
|
+
@dot_string
|
|
191
|
+
|
|
192
|
+
def initialize( params = {}, option_list = GRAPH_OPTS )
|
|
193
|
+
super( params, option_list )
|
|
194
|
+
@nodes = params['nodes'] ? params['nodes'] : []
|
|
195
|
+
@dot_string = 'graph'
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def each_node
|
|
199
|
+
@nodes.each{ |i| yield i }
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def << ( thing )
|
|
203
|
+
@nodes << thing
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def push( thing )
|
|
207
|
+
@nodes.push( thing )
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def pop
|
|
211
|
+
@nodes.pop
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def to_s( t = '' )
|
|
215
|
+
hdr = t + "#{@dot_string} #{@name} {\n"
|
|
216
|
+
|
|
217
|
+
options = @options.to_a.collect{ |name, val|
|
|
218
|
+
val && name != 'label' ?
|
|
219
|
+
t + $tab + "#{name} = #{val}" :
|
|
220
|
+
name ? t + $tab + "#{name} = \"#{val}\"" : nil
|
|
221
|
+
}.compact.join( "\n" ) + "\n"
|
|
222
|
+
|
|
223
|
+
nodes = @nodes.collect{ |i|
|
|
224
|
+
i.to_s( t + $tab )
|
|
225
|
+
}.join( "\n" ) + "\n"
|
|
226
|
+
hdr + options + nodes + t + "}\n"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# this is graph
|
|
231
|
+
class DOTDigraph < DOTSubgraph
|
|
232
|
+
def initialize( params = {}, option_list = GRAPH_OPTS )
|
|
233
|
+
super( params, option_list )
|
|
234
|
+
@dot_string = 'digraph'
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# this is edge
|
|
239
|
+
class DOTEdge < DOTElement
|
|
240
|
+
attr_accessor :from, :to
|
|
241
|
+
def initialize( params = {}, option_list = EDGE_OPTS )
|
|
242
|
+
super( params, option_list )
|
|
243
|
+
@from = params['from'] ? params['from'] : nil
|
|
244
|
+
@to = params['to'] ? params['to'] : nil
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def edge_link; '--'; end
|
|
248
|
+
def to_s( t = '' )
|
|
249
|
+
t + "#{@from} #{edge_link} #{to} [\n" +
|
|
250
|
+
@options.to_a.collect{ |i|
|
|
251
|
+
i[1] && i[0] != 'label' ?
|
|
252
|
+
t + $tab + "#{i[0]} = #{i[1]}" :
|
|
253
|
+
i[1] ? t + $tab + "#{i[0]} = \"#{i[1]}\"" : nil
|
|
254
|
+
}.compact.join( "\n" ) + "\n" + t + "]\n"
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
class DOTDirectedEdge < DOTEdge
|
|
259
|
+
def edge_link; '->'; end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
data/lib/rgl/topsort.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'rgl/traversal'
|
|
2
|
+
|
|
3
|
+
module RGL
|
|
4
|
+
# Topological Sort Iterator
|
|
5
|
+
#
|
|
6
|
+
# The topological sort algorithm creates a linear ordering
|
|
7
|
+
# of the vertices such that if edge (u,v) appears in the graph,
|
|
8
|
+
# then u comes before v in the ordering. The graph must
|
|
9
|
+
# be a directed acyclic graph (DAG).
|
|
10
|
+
#
|
|
11
|
+
# The iterator can also be applied to undirected graph or a DG graph which
|
|
12
|
+
# contains a cycle. In this case the Iterator does not reach all vertices. The
|
|
13
|
+
# implementation of acyclic? uses this fact.
|
|
14
|
+
class TopsortIterator
|
|
15
|
+
include GraphIterator
|
|
16
|
+
def initialize(g)
|
|
17
|
+
super g
|
|
18
|
+
set_to_begin
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set_to_begin # :nodoc:
|
|
22
|
+
@waiting = Array.new
|
|
23
|
+
@inDegrees = Hash.new(0)
|
|
24
|
+
|
|
25
|
+
graph.each_vertex do |u|
|
|
26
|
+
@inDegrees[u] = 0 unless @inDegrees.has_key? u
|
|
27
|
+
graph.each_adjacent(u) do |v|
|
|
28
|
+
@inDegrees[v] += 1
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
@inDegrees.each_pair do |v, indegree |
|
|
32
|
+
@waiting.push(v) if indegree.zero?
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def basic_forward # :nodoc:
|
|
37
|
+
u = @waiting.pop
|
|
38
|
+
graph.each_adjacent(u) do |v|
|
|
39
|
+
@inDegrees[v] -= 1
|
|
40
|
+
@waiting.push(v) if @inDegrees[v].zero?
|
|
41
|
+
end
|
|
42
|
+
u
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def at_beginning?; true; end # :nodoc: FIXME
|
|
46
|
+
def at_end?; @waiting.empty?; end # :nodoc:
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module Graph
|
|
50
|
+
# Returns a TopsortIterator.
|
|
51
|
+
def topsort_iterator
|
|
52
|
+
TopsortIterator.new(self)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns true if the graph contains no cycle. This is only meaningful for
|
|
56
|
+
# directed graphs. Returns false for undirected graph.
|
|
57
|
+
def acyclic?
|
|
58
|
+
topsort_iterator.length == num_vertices
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# == transitive_closure
|
|
2
|
+
#
|
|
3
|
+
# The transitive closure of a graph G = (V,E) is a graph G* = (V,E*) such that E*
|
|
4
|
+
# contains an edge (u,v) if and only if G contains a path (of at least one edge)
|
|
5
|
+
# from u to v. The transitive_closure() function transforms the input graph g into
|
|
6
|
+
# the transitive closure graph tc.
|
|
7
|
+
require 'rgl/adjacency'
|
|
8
|
+
|
|
9
|
+
module RGL
|
|
10
|
+
module Graph
|
|
11
|
+
# Floyd-Warshal algorithm which should be O(n^3) where n is the number of
|
|
12
|
+
# nodes. We can probably work a bit on the constant factors!
|
|
13
|
+
#
|
|
14
|
+
# In BGL there is an algorithm with time complexity (worst-case) O(|V||E|)
|
|
15
|
+
# (see BOOST_DOC/transitive_closure.html) based on the detection of strong components.
|
|
16
|
+
def transitive_closure_floyd_warshal
|
|
17
|
+
raise NotDirectedError, "transitive_closure makes only sense for directed graphs." unless directed?
|
|
18
|
+
tc = to_adjacency # direct links
|
|
19
|
+
|
|
20
|
+
# indirect links
|
|
21
|
+
each_vertex do |vi|
|
|
22
|
+
each_vertex do |vj|
|
|
23
|
+
each_vertex do |vk|
|
|
24
|
+
unless tc.has_edge?(vi,vj)
|
|
25
|
+
tc.add_edge(vi, vj) if has_edge?(vi,vk) and has_edge?(vk,vj)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
tc
|
|
31
|
+
end
|
|
32
|
+
alias_method :transitive_closure, :transitive_closure_floyd_warshal
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# This files defines the basic graph traversal algorithm DFS and BFS
|
|
2
|
+
# search. They are implemented as an RGL::GraphIterator which is a Stream of
|
|
3
|
+
# vertices of a given graph. The streams are not reversable.
|
|
4
|
+
#
|
|
5
|
+
# Beside being an iterator in the sense of the Stream mixin RGL::BFSIterator and
|
|
6
|
+
# RGL::DFSIterator follow the BGL Visitor Concepts (see
|
|
7
|
+
# BOOST_DOC/visitor_concepts.html) in a slightly modified fashion (especially
|
|
8
|
+
# for the DFSIterator).
|
|
9
|
+
|
|
10
|
+
require 'rgl/base'
|
|
11
|
+
require 'rubygems' rescue LoadError # If using stream gem
|
|
12
|
+
require 'stream'
|
|
13
|
+
|
|
14
|
+
module RGL
|
|
15
|
+
module GraphWrapper # :nodoc:
|
|
16
|
+
attr_accessor :graph
|
|
17
|
+
|
|
18
|
+
# Creates a new GraphWrapper on _graph_.
|
|
19
|
+
def initialize(graph); @graph = graph; end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A GraphIterator is the abstract superclass of all Iterators on graphs. Each
|
|
23
|
+
# such iterator should implement the protocol defined in module Stream.
|
|
24
|
+
module GraphIterator
|
|
25
|
+
include Stream
|
|
26
|
+
include GraphWrapper
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Module GraphVisitor defines the BGL BFS Visitor Concept (see
|
|
30
|
+
# BOOST_DOC/BFSVisitor.html).
|
|
31
|
+
#
|
|
32
|
+
# Visitors provide a mechanism for extending an algorithm; for customizing
|
|
33
|
+
# what is done at each step of the algorithm. They allow the user to insert
|
|
34
|
+
# their own operations at various steps within a graph algorithm.
|
|
35
|
+
#
|
|
36
|
+
# Graph algorithms typically have multiple event points where one may want to
|
|
37
|
+
# insert a call-back. Therefore visitors have several methods that correspond
|
|
38
|
+
# to the various event points. Each algorithm has a different set of event
|
|
39
|
+
# points. The following are are common to both DFS and BFS search.
|
|
40
|
+
#
|
|
41
|
+
# * examine_vertex
|
|
42
|
+
# * finish_vertex
|
|
43
|
+
# * examine_edge
|
|
44
|
+
# * tree_edge
|
|
45
|
+
# * back_edge
|
|
46
|
+
# * forward_edge
|
|
47
|
+
#
|
|
48
|
+
# These methods are all called handle_* and can be set to appropriate blocks
|
|
49
|
+
# using the methods set_*_event_handler, which are defined for each event
|
|
50
|
+
# mentioned above.
|
|
51
|
+
#
|
|
52
|
+
# As an alternative you can also overide the handle_* methods
|
|
53
|
+
# in a subclass to configure the algorithm (as an example see
|
|
54
|
+
# TarjanSccVisitor).
|
|
55
|
+
#
|
|
56
|
+
# During a graph traversal vertices are *colored* using the colors :GRAY
|
|
57
|
+
# (when waiting) and :BLACK when finished. All other vertices are :WHITE.
|
|
58
|
+
# The color_map is also maintained in the visitor.
|
|
59
|
+
module GraphVisitor
|
|
60
|
+
include GraphWrapper
|
|
61
|
+
attr_reader :color_map
|
|
62
|
+
|
|
63
|
+
# Create a new GraphVisitor on _graph_.
|
|
64
|
+
def initialize(graph)
|
|
65
|
+
super graph
|
|
66
|
+
reset
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Mark each vertex unvisited (i.e. :WHITE)
|
|
70
|
+
def reset
|
|
71
|
+
@color_map = Hash.new(:WHITE)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns true if vertex _v_ is colored :BLACK (i.e. finished)
|
|
75
|
+
def finished_vertex? v
|
|
76
|
+
@color_map[v] == :BLACK
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Attach a map to the visitor which records the distance of a visited
|
|
80
|
+
# vertex to the start vertex.
|
|
81
|
+
#
|
|
82
|
+
# This is similar to BGLs distance_recorder (see
|
|
83
|
+
# BOOST_DOC/distance_recorder.html).
|
|
84
|
+
#
|
|
85
|
+
# After the distance_map is attached, the visitor has a new method
|
|
86
|
+
# distance_to_root, which answers the distance to the start vertex.
|
|
87
|
+
def attach_distance_map(map=Hash.new(0))
|
|
88
|
+
@dist_map = map
|
|
89
|
+
class << self
|
|
90
|
+
def handle_tree_edge(u,v)
|
|
91
|
+
super
|
|
92
|
+
@dist_map[v] = @dist_map[u] + 1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Answer the distance to the start vertex.
|
|
96
|
+
def distance_to_root(v)
|
|
97
|
+
@dist_map[v]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Shell we follow the edge (u,v) i.e. v has color :WHITE
|
|
103
|
+
def follow_edge?(u,v) # :nodoc:
|
|
104
|
+
@color_map[v] == :WHITE
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# == Visitor Event Points
|
|
108
|
+
def self.def_event_handler(m)
|
|
109
|
+
params = m =~ /edge/ ? "u,v" : "u"
|
|
110
|
+
self.class_eval %{
|
|
111
|
+
def handle_#{m}(#{params})
|
|
112
|
+
@#{m}_event_handler.call(#{params}) if defined? @#{m}_event_handler
|
|
113
|
+
end
|
|
114
|
+
def set_#{m}_event_handler(&b)
|
|
115
|
+
@#{m}_event_handler = b
|
|
116
|
+
end
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
%w[examine_vertex finish_vertex examine_edge tree_edge back_edge forward_edge].each do |m|
|
|
121
|
+
def_event_handler(m)
|
|
122
|
+
end
|
|
123
|
+
end # GraphVisitor
|
|
124
|
+
|
|
125
|
+
# A BFSIterator can be used to traverse a graph from a given start vertex in
|
|
126
|
+
# breath first search order. Since the Iterator also mixins the GraphVisitor
|
|
127
|
+
# it provides all event points defined there.
|
|
128
|
+
#
|
|
129
|
+
# The vertices which are not yet visited are hold in the queue
|
|
130
|
+
# @waiting. During the traversal vertices are *colored* using the colors :GRAY
|
|
131
|
+
# (when waiting) and :BLACK when finished. All other vertices are :WHITE.
|
|
132
|
+
#
|
|
133
|
+
# For more doc see the BGL BFS Visitor Concept
|
|
134
|
+
# BOOST_DOC/BFSVisitor.html.
|
|
135
|
+
#
|
|
136
|
+
# See the implementation of bfs_search_tree_from for an example usage.
|
|
137
|
+
class BFSIterator
|
|
138
|
+
include GraphIterator
|
|
139
|
+
include GraphVisitor
|
|
140
|
+
attr_accessor :start_vertex
|
|
141
|
+
|
|
142
|
+
# Create a new BFSIterator on _graph_ starting at vertex _start_.
|
|
143
|
+
def initialize(graph, start=graph.detect{|x| true})
|
|
144
|
+
super graph
|
|
145
|
+
@start_vertex = start
|
|
146
|
+
set_to_begin
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Returns true if the @color_map has only one entry (for the start vertex).
|
|
150
|
+
def at_beginning?; @color_map.size == 1; end # :nodoc:
|
|
151
|
+
|
|
152
|
+
# Returns true if @waiting is empty.
|
|
153
|
+
def at_end?; @waiting.empty?; end
|
|
154
|
+
|
|
155
|
+
# Reset the iterator to the initial state (i.e. at_beginning? == true).
|
|
156
|
+
def set_to_begin
|
|
157
|
+
color_map[@start_vertex] = :GRAY
|
|
158
|
+
@waiting = [@start_vertex] # a queue
|
|
159
|
+
handle_tree_edge(nil,@start_vertex) # discovers start vertex
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def basic_forward # :nodoc:
|
|
163
|
+
u = next_vertex
|
|
164
|
+
handle_examine_vertex(u)
|
|
165
|
+
graph.each_adjacent(u) { |v|
|
|
166
|
+
handle_examine_edge(u,v)
|
|
167
|
+
if follow_edge? u,v # (u,v) is a tree edge
|
|
168
|
+
handle_tree_edge(u,v) # also discovers v
|
|
169
|
+
color_map[v] = :GRAY # color of v was :WHITE
|
|
170
|
+
@waiting.push v
|
|
171
|
+
else # (u,v) is a non tree edge
|
|
172
|
+
if color_map[v] == :GRAY
|
|
173
|
+
handle_back_edge(u,v) # (u,v) has gray target
|
|
174
|
+
else
|
|
175
|
+
handle_forward_edge(u,v) # (u,v) has black target
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
}
|
|
179
|
+
color_map[u] = :BLACK
|
|
180
|
+
handle_finish_vertex(u) # finish vertex
|
|
181
|
+
u
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
protected
|
|
185
|
+
|
|
186
|
+
def next_vertex () # :nodoc:
|
|
187
|
+
# waiting is a queue
|
|
188
|
+
@waiting.shift
|
|
189
|
+
end
|
|
190
|
+
end # BFSIterator
|
|
191
|
+
|
|
192
|
+
module Graph
|
|
193
|
+
# Returns a BFSIterator staring at vertex _v_.
|
|
194
|
+
def bfs_iterator (v=self.detect {|x| true})
|
|
195
|
+
BFSIterator.new(self,v)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns a DirectedAdjacencyGraph which represents a BFS search tree
|
|
199
|
+
# starting at _v_. This methods uses the tree_edge_event of BFSIterator to
|
|
200
|
+
# record all tree edges of the search tree in the result.
|
|
201
|
+
def bfs_search_tree_from(v)
|
|
202
|
+
require 'rgl/adjacency'
|
|
203
|
+
bfs = bfs_iterator(v)
|
|
204
|
+
tree = DirectedAdjacencyGraph.new
|
|
205
|
+
bfs.set_tree_edge_event_handler {|from, to|
|
|
206
|
+
tree.add_edge from, to
|
|
207
|
+
}
|
|
208
|
+
bfs.set_to_end # does the search
|
|
209
|
+
tree
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Iterator for a depth first search starting at a given vertex. The only
|
|
214
|
+
# difference to BFSIterator is that @waiting is a stack instead of a queue.
|
|
215
|
+
#
|
|
216
|
+
# Note that this is different to DFSVisitor which is used in the recursive
|
|
217
|
+
# version for depth first search (see depth_first_search).
|
|
218
|
+
class DFSIterator < BFSIterator
|
|
219
|
+
def next_vertex
|
|
220
|
+
# waiting is a stack
|
|
221
|
+
@waiting.pop
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# A DFSVisitor is needed by the depth_first_search and
|
|
226
|
+
# depth_first_visit methods of a graph. Besides the eventpoint of GraphVisitor
|
|
227
|
+
# it provides an additional eventpoint start_vertex, which is called when a
|
|
228
|
+
# depth_first_search starts a new subtree of the depth first forest defined by
|
|
229
|
+
# the search.
|
|
230
|
+
#
|
|
231
|
+
# Note that the discover_vertex event defined in the BGL DFSVisitor
|
|
232
|
+
# (BOOST_DOC/DFSVisitor.html) is not provided. Use examine_vertex instead
|
|
233
|
+
# which is also defined in the common mixin GraphVisitor of DFSVisitor,
|
|
234
|
+
# DFSIterator and BFSIterator.
|
|
235
|
+
class DFSVisitor
|
|
236
|
+
include GraphVisitor
|
|
237
|
+
|
|
238
|
+
GraphVisitor.def_event_handler("start_vertex")
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
module Graph
|
|
242
|
+
# Returns a DFSIterator staring at vertex _v_.
|
|
243
|
+
def dfs_iterator (v=self.detect {|x| true})
|
|
244
|
+
DFSIterator.new(self,v)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Do a recursive DFS search on the whole graph. If a block is passed, it is
|
|
248
|
+
# called on each _finish_vertex_ event. See strongly_connected_components for
|
|
249
|
+
# an example usage.
|
|
250
|
+
def depth_first_search (vis = DFSVisitor.new(self),&b)
|
|
251
|
+
each_vertex do |u|
|
|
252
|
+
unless vis.finished_vertex? u
|
|
253
|
+
vis.handle_start_vertex u
|
|
254
|
+
depth_first_visit(u,vis,&b)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Start a depth first search at vertex _u_. The block _b_ is called on each
|
|
260
|
+
# finish_vertex event.
|
|
261
|
+
def depth_first_visit (u, vis = DFSVisitor.new(self), &b)
|
|
262
|
+
vis.color_map[u] = :GRAY
|
|
263
|
+
vis.handle_examine_vertex(u)
|
|
264
|
+
each_adjacent(u) { |v|
|
|
265
|
+
vis.handle_examine_edge(u,v)
|
|
266
|
+
if vis.follow_edge? u,v # (u,v) is a tree edge
|
|
267
|
+
vis.handle_tree_edge(u,v) # also discovers v
|
|
268
|
+
vis.color_map[v] = :GRAY # color of v was :WHITE
|
|
269
|
+
depth_first_visit(v, vis, &b)
|
|
270
|
+
else # (u,v) is a non tree edge
|
|
271
|
+
if vis.color_map[v] == :GRAY
|
|
272
|
+
vis.handle_back_edge(u,v) # (u,v) has gray target
|
|
273
|
+
else
|
|
274
|
+
vis.handle_forward_edge(u,v) # (u,v) is a cross or forward edge
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
}
|
|
278
|
+
vis.color_map[u] = :BLACK
|
|
279
|
+
vis.handle_finish_vertex(u) # finish vertex
|
|
280
|
+
b.call(u)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
=begin
|
|
285
|
+
def acyclic?
|
|
286
|
+
has_cycle = false
|
|
287
|
+
dfs = DFSIterator.new(self)
|
|
288
|
+
dfs.set_back_edge_event {has_cycle = true}
|
|
289
|
+
dfs_each(dfs) do |x|
|
|
290
|
+
puts x,has_cycle,dfs.inspect
|
|
291
|
+
return false if has_cycle
|
|
292
|
+
end
|
|
293
|
+
true
|
|
294
|
+
end
|
|
295
|
+
=end
|
|
296
|
+
end
|