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