yargi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENCE ADDED
@@ -0,0 +1,25 @@
1
+ = Licence
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 2009 Bernard Lambeau and the University of Louvain
6
+ (Universite Catholique de Louvain, Louvain-la-Neuve, Belgium)
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+
data/README ADDED
@@ -0,0 +1,79 @@
1
+ = Yargi
2
+
3
+ Yargi aims at providing a powerful mutable digraph implementation. As reflected by its name,
4
+ Yargi is Yet Another (Ruby) Graph Implementation: we all have implemented a graph data structure
5
+ in almost every language that we use, probably many times. The reason is probably that each
6
+ implementation has its own design decisions that makes the data-structure well-designed for
7
+ the task at hand, or not. Below are the main design decisions made by Yargi; this way you can
8
+ easily see if it fits your needs.
9
+
10
+ [Digraph, Vertex and Edge] Unlike {RGL}[http://rubyforge.org/projects/rgl/], Yargi implements
11
+ graph components through concrete classes, not modules (that is, in
12
+ a standard-but-closed way). See Digraph, Digraph::Vertex and Digraph::Edge
13
+ respectively.
14
+ [Markable pattern] Graphs, vertices and edges are markable through a Hash-like API: users can
15
+ install their own key/value pairs on each graph element. When keys are Symbol
16
+ objects, accessors are automatically generated to provide a friendly object-oriented
17
+ API (this feature must be used with care, for obvious reasons).
18
+ Example:
19
+ graph = Yargi::Digraph.new
20
+ v1 = graph.add_vertex(:kind => "simple vertex")
21
+ puts v1.kind # prints "simple vertex"
22
+
23
+ [Typed elements] Graph elements (vertices and edges) can be tagged with your own modules (at creation,
24
+ or later). This is the standard Yargi way to apply a 'select-and-do' feature described
25
+ below.
26
+ Example:
27
+ module Diamond; end
28
+ module Circle; end
29
+ graph = Yargi::Digraph.new
30
+ graph.add_n_vertices(5, Diamond) do |v,i|
31
+ v[:color] = (i%2==0 ? 'red' : 'blue')
32
+ end
33
+ graph.add_n_vertices(5, Circle)
34
+ graph.connect(Diamond, Circle) # connect all diamonds to all circles
35
+
36
+ [Selection mechanism] Yargi helps you finding the nodes and edges you are looking for through a
37
+ declarative selection mechanism: almost all methods that return a set of
38
+ vertices or edges (Vertex.out_edges, for example) accept a predicate
39
+ argument to filter the result, module names being most-used shortcuts.
40
+ See Yargi::Predicate for details.
41
+ Example:
42
+ [... previous example continued ...]
43
+ graph.vertices(Diamond) # selects all diamonds
44
+ graph.vertices(Diamond){|v| v.color=='blue'} # selects blue diamonds only
45
+
46
+ # Proc variant, find sink states
47
+ sink = Yargi.predicate {|v| v.out_edges.empty?}
48
+ graph.vertices(sink & Circle) # select all Circle sink states (no one)
49
+
50
+ # Or selection
51
+ is_blue = Yargi.predicate {|v| Diamond===v and v.color=='blue'}
52
+ graph.vertices(is_blue|Circle) # select blue diamonds and circles
53
+
54
+ [VertexSet and EdgeSet] The selection mechanism always returns arrays ... being instances
55
+ of Yargi::VertexSet and Yargi::EdgeSet. These classes help you walking
56
+ your graph easily.
57
+ Example:
58
+ [... previous example continued ...]
59
+ circles = graph.vertices(Diamond).adjacent
60
+ puts graph.vertices(Circle)==circles # puts true
61
+
62
+ [Select-and-do] Many graph methods accept sets of vertices and edges as well as selection queries
63
+ to work. Instead of connecting one source to one target at a time by passing the
64
+ vertices, describe what you want and Yardi does it. Add, connect, remove, mark,
65
+ reconnect many vertices/edges with a single method call !
66
+ Example:
67
+ graph.vertices(Diamond).add_marks(:label => '', :shape => 'diamond')
68
+ graph.vertices(Circle).add_marks(:label => '', :shape => 'circle')
69
+ puts graph.to_dot
70
+ graph.remove_vertices(Circle) # remove all circles
71
+
72
+ [Mutable graphs] Graphs here are mutable, mutable and mutable and this is the reason why Yargi
73
+ exists. It comes from a project where manipulating graphs by reconnecting edges,
74
+ removing vertices is the norm, not the exception.
75
+ [Complexity don't care] The digraph implementation uses an incident list data structure. This graph
76
+ library has not been designed with efficiency in mind so that complexities
77
+ are not documented nor guaranteed. That is not to say that improvements are
78
+ not welcome, of course.
79
+ [Ruby minded] Because we love that language!
@@ -0,0 +1,287 @@
1
+ require 'yargi/digraph_vertex'
2
+ require 'yargi/digraph_edge'
3
+
4
+ module Yargi
5
+
6
+ #
7
+ # Directed graph implementation.
8
+ #
9
+ # * All selection methods (vertices, each_vertex, edges, each_edge) implement the
10
+ # predicate-selection mechanism (they accept a predicate filter as well as a
11
+ # predicate block, with a AND semantics when used conjointly).
12
+ # * Removal methods (remove_vertex, remove_vertices, remove_edge, remove_edges)
13
+ # accept varying arguments that are automatically converted to set of vertices
14
+ # or edges. Recognized arguments are Vertex and Edge instances, VertexSet and
15
+ # EdgeSet instances, Array instances, and predicates. When multiple arguments
16
+ # are used conjointly, a OR-semantics is applied.
17
+ #
18
+ # Example:
19
+ # graph.remove_vertices(Circle, Diamond) # removes all vertices tagged as Circle OR Diamond
20
+ # graph.remove_vertices(Yargi::ALL & Circle & Diamond) # remove vertices that are both Circle and Diamond
21
+ #
22
+ class Digraph
23
+ include Yargi::Markable
24
+
25
+ # Creates an empty graph instance
26
+ def initialize
27
+ @vertices = VertexSet[]
28
+ @edges = EdgeSet[]
29
+ @marks = {}
30
+ end
31
+
32
+ ### Vertex management ################################################
33
+
34
+ # Returns all graph vertices for which the 'filter and block' predicate
35
+ # evaluates to true (see Yargi::Predicate).
36
+ def vertices(filter=nil, &block)
37
+ @vertices.filter(filter, &block)
38
+ end
39
+
40
+ # Calls block on each graph vertex for with the 'filter and block' predicate
41
+ # evaluates to true.
42
+ def each_vertex(filter=nil, &block)
43
+ return unless block_given?
44
+ if filter.nil?
45
+ @vertices.each &block
46
+ else
47
+ vertices(filter).each &block
48
+ end
49
+ end
50
+
51
+ # Adds a vertex. _args_ can be module instances or hashes,
52
+ # which are all installed on the vertex _v_ using <tt>v.tag</tt>
53
+ # and <tt>v.add_marks</tt>, respectively.
54
+ def add_vertex(*args)
55
+ vertex = Digraph::Vertex.new(self, @vertices.length)
56
+ apply_arg_conventions(vertex, args)
57
+ @vertices << vertex
58
+ vertex
59
+ end
60
+
61
+ # Creates n vertices. _args_ can be module instances or hashes,
62
+ # which are all installed on vertices _v_ using <tt>v.tag</tt>
63
+ # and <tt>v.add_marks</tt>, respectively. If a block is given,
64
+ # it is called after each vertex creation. The vertex is passed
65
+ # as first argument and the iteration index (from 0 to n-1) as
66
+ # second one.
67
+ def add_n_vertices(n, *args)
68
+ vertices = []
69
+ n.times do |i|
70
+ vertex = add_vertex(*args)
71
+ vertices << vertex
72
+ yield vertex, i if block_given?
73
+ end
74
+ VertexSet.new(vertices)
75
+ end
76
+
77
+ # Removes all vertices returned by evaluating the _vertices_ selection
78
+ # expression.
79
+ def remove_vertices(*vertices)
80
+ vertices = to_vertices(*vertices).sort{|v1,v2| v2<=>v1}
81
+ vertices.each do |vertex|
82
+ remove_edges(vertex.in_edges+vertex.out_edges)
83
+ @vertices.delete_at(vertex.index)
84
+ vertex.index=-1
85
+ end
86
+ @vertices.each_with_index {|v,i| v.index=i}
87
+ self
88
+ end
89
+ alias :remove_vertex :remove_vertices
90
+
91
+ ### Edge management ##################################################
92
+
93
+ # Returns all graph edges for which the 'filter and block' predicate
94
+ # evaluates to true (see Yargi::Predicate).
95
+ def edges(filter=nil, &block)
96
+ @edges.filter(filter, &block)
97
+ end
98
+
99
+ # Calls block on each graph edge for with the 'filter and block' predicate
100
+ # evaluates to true.
101
+ def each_edge(filter=nil, &block)
102
+ return unless block_given?
103
+ if filter.nil?
104
+ @edges.each &block
105
+ else
106
+ edges(filter).each &block
107
+ end
108
+ end
109
+
110
+ # Connects source to target state(s). _source_ and _target_ may be any
111
+ # selection expression that can lead to vertex sets. _args_ can be module
112
+ # instances or hashes, which are all installed on edges _e_ using
113
+ # <tt>e.tag</tt> and <tt>e.add_marks</tt>, respectively.
114
+ def add_edge(source, target, *args)
115
+ if Vertex===source and Vertex===target
116
+ edge = Digraph::Edge.new(self, @edges.length, source, target)
117
+ apply_arg_conventions(edge, args)
118
+ source.add_out_edge(edge)
119
+ target.add_in_edge(edge)
120
+ @edges << edge
121
+ edge
122
+ else
123
+ sources, targets = to_vertices(source), to_vertices(target)
124
+ created = EdgeSet[]
125
+ sources.each do |src|
126
+ targets.each do |trg|
127
+ created << add_edge(src, trg, *args)
128
+ end
129
+ end
130
+ created
131
+ end
132
+ end
133
+ alias :connect :add_edge
134
+
135
+ # Adds many edges at once
136
+ def add_edges(*extremities)
137
+ extremities.collect do |extr|
138
+ add_edge(extr[0], extr[1], *extr[2..-1])
139
+ end
140
+ end
141
+ alias :connect_all :add_edges
142
+
143
+ # Removes all edges returned by evaluating the _edges_ selection
144
+ # expression.
145
+ def remove_edges(*edges)
146
+ edges = to_edges(edges).sort{|e1,e2| e2<=>e1}
147
+ edges.each do |edge|
148
+ edge.source.remove_out_edge(edge)
149
+ edge.target.remove_in_edge(edge)
150
+ @edges.delete_at(edge.index)
151
+ edge.index = -1
152
+ end
153
+ @edges.each_with_index {|edge,i| edge.index=i}
154
+ self
155
+ end
156
+ alias :remove_edge :remove_edges
157
+
158
+ # Reconnects some edge(s). _source_ and _target_ are expected to be
159
+ # Vertex instances, or nil. _edges_ may be any selection expression
160
+ # that can be converted to an edge set. This method reconnects all
161
+ # edges to the specified source and target vertices (at least one is
162
+ # expected not to be nil).
163
+ def reconnect(edges, source, target)
164
+ raise ArgumentError, "Vertices expected as source and target"\
165
+ unless (source.nil? or Vertex===source) and (target.nil? or Vertex===target)
166
+ to_edges(edges).each do |edge|
167
+ if source
168
+ edge.source.remove_out_edge(edge)
169
+ source.add_out_edge(edge)
170
+ end
171
+ if target
172
+ edge.target.remove_in_edge(edge)
173
+ target.add_in_edge(edge)
174
+ end
175
+ edge.reconnect(source, target)
176
+ edge
177
+ end
178
+ end
179
+
180
+ ### Standard exports #################################################
181
+
182
+ # Encodes this graph for dot graphviz
183
+ def to_dot(buffer='')
184
+ buffer << "digraph G {\n"
185
+ buffer << " graph[#{to_dot_attributes(self.to_h(true))}]\n"
186
+ each_vertex do |v|
187
+ buffer << " V#{v.index} [#{to_dot_attributes(v.to_h(true))}]\n"
188
+ end
189
+ each_edge do |e|
190
+ buffer << " V#{e.source.index} -> V#{e.target.index} [#{to_dot_attributes(e.to_h(true))}]\n"
191
+ end
192
+ buffer << "}\n"
193
+ end
194
+
195
+ ### Argument conventions #############################################
196
+ protected
197
+
198
+ # Converts a hash to dot attributes
199
+ def to_dot_attributes(hash)
200
+ # TODO: fix uncompatible key names
201
+ # TODO: some values must be encoded (backquoting and the like)
202
+ buffer = ""
203
+ hash.each_pair do |k,v|
204
+ buffer << "#{k}=\"#{v}\""
205
+ end
206
+ buffer
207
+ end
208
+
209
+ # Checks if _arg_ looks like an element set
210
+ def looks_a_set?(arg)
211
+ Array===arg or ElementSet===arg
212
+ end
213
+
214
+ # Checks graph sanity
215
+ def check_sanity
216
+ @vertices.each_with_index do |v,i|
217
+ raise "Removed vertex in vertex list" unless v.index==i
218
+ v.in_edges.each do |ine|
219
+ raise "Removed edge in vertex incoming edges" if ine.index<0
220
+ raise "Vertex and edge don't agree on target" unless ine.target==v
221
+ end
222
+ v.out_edges.each do |oute|
223
+ raise "Removed edge in vertex outgoing edges" if oute.index<0
224
+ raise "Vertex and edge don't agree on source" unless oute.source==v
225
+ end
226
+ end
227
+ @edges.each_with_index do |e,i|
228
+ raise "Removed edge in edge list" unless e.index==i
229
+ raise "Edge in-connected to a removed vertex" if e.source.index<0
230
+ raise "Edge out-connected to a removed vertex" if e.target.index<0
231
+ end
232
+ end
233
+
234
+ # Applies argument conventions about selection of vertices
235
+ def to_vertices(*args)
236
+ selected = args.collect do |arg|
237
+ case arg
238
+ when VertexSet
239
+ arg
240
+ when Array
241
+ arg.collect{|v| to_vertices(v)}.flatten.uniq
242
+ when Digraph::Vertex
243
+ [arg]
244
+ else
245
+ pred = Predicate.to_predicate(arg)
246
+ vertices(pred)
247
+ end
248
+ end.flatten.uniq
249
+ VertexSet.new(selected)
250
+ end
251
+
252
+ # Applies argument conventions about selection of edges
253
+ def to_edges(*args)
254
+ selected = args.collect do |arg|
255
+ case arg
256
+ when EdgeSet
257
+ arg
258
+ when Array
259
+ arg.collect{|v| to_edges(v)}.flatten.uniq
260
+ when Digraph::Edge
261
+ [arg]
262
+ else
263
+ pred = Predicate.to_predicate(arg)
264
+ edges(pred)
265
+ end
266
+ end.flatten.uniq
267
+ EdgeSet.new(selected)
268
+ end
269
+
270
+ # Applies argument conventions on _element_
271
+ def apply_arg_conventions(element, args)
272
+ args.each do |arg|
273
+ case arg
274
+ when Module
275
+ element.tag(arg)
276
+ when Hash
277
+ element.add_marks(arg)
278
+ else
279
+ raise ArgumentError, "Unable to apply argument conventions on #{arg.inspect}", caller
280
+ end
281
+ end
282
+ element
283
+ end
284
+
285
+ end # class Digraph
286
+
287
+ end
@@ -0,0 +1,81 @@
1
+ module Yargi
2
+ class Digraph
3
+
4
+ #
5
+ # Edge inside a digraph
6
+ #
7
+ # Methods reconnect, index= are provided for Digraph itself and are not
8
+ # intended to be used directly. Probably unexpectedly, source and target
9
+ # writers are provided as reconnection shortcuts and can be used by users.
10
+ #
11
+ class Edge
12
+ include Yargi::Markable
13
+
14
+ # Owning graph
15
+ attr_reader :graph
16
+ alias :digraph :graph
17
+
18
+ # Index in the vertices list of the owner
19
+ attr_accessor :index
20
+
21
+ # Source vertex
22
+ attr_reader :source
23
+
24
+ # Target vertex
25
+ attr_reader :target
26
+
27
+ # Creates an edge instance
28
+ def initialize(graph, index, source, target)
29
+ @graph, @index = graph, index
30
+ @source, @target = source, target
31
+ end
32
+
33
+
34
+ ### Pseudo-protected section ##########################################
35
+
36
+ # Reconnects source and target
37
+ def reconnect(source, target)
38
+ @source = source if source
39
+ @target = target if target
40
+ end
41
+
42
+
43
+ ### Query section #######################################################
44
+
45
+ # Returns edge extremities
46
+ def extremities
47
+ VertexSet[source, target]
48
+ end
49
+
50
+ # Shortcut for digraph.reconnect(edge, source, nil)
51
+ def source=(source)
52
+ @graph.reconnect(self, source, nil)
53
+ end
54
+
55
+ # Shortcut for digraph.reconnect(edge, nil, target)
56
+ def target=(target)
57
+ @graph.reconnect(self, nil, target)
58
+ end
59
+
60
+
61
+ ### Sort, Hash, etc. section ############################################
62
+
63
+ # Compares indexes
64
+ def <=>(other)
65
+ return nil unless Edge===other and self.graph==other.graph
66
+ self.index <=> other.index
67
+ end
68
+
69
+
70
+ ### Export section ######################################################
71
+
72
+ # Returns a string representation
73
+ def to_s; "e#{index}:#{source.to_s}->#{target.to_s}" end
74
+
75
+ # Inspects the vertex
76
+ def inspect; "e#{index}:#{source.inspect}->#{target.inspect}" end
77
+
78
+ end # class Edge
79
+
80
+ end
81
+ end
@@ -0,0 +1,98 @@
1
+ module Yargi
2
+ class Digraph
3
+
4
+ #
5
+ # Vertex inside a digraph.
6
+ #
7
+ # Methods add_in_edge, remove_in_edge, add_out_edge, remove_out_edge and index=
8
+ # are provided for Digraph itself and are not intended to be used directly.
9
+ #
10
+ class Vertex
11
+ include Yargi::Markable
12
+
13
+ # Owning graph
14
+ attr_reader :graph
15
+ alias :digraph :graph
16
+
17
+ # Index in the vertices list of the owner
18
+ attr_accessor :index
19
+
20
+ # Creates a vertex instance
21
+ def initialize(graph, index)
22
+ @graph, @index = graph, index
23
+ @in_edges, @out_edges = EdgeSet[], EdgeSet[]
24
+ end
25
+
26
+
27
+ ### Query section #######################################################
28
+
29
+ # Returns a copy of the incoming edges list.
30
+ def in_edges(filter=nil, &block)
31
+ @in_edges.filter(filter, &block)
32
+ end
33
+
34
+ # Returns a copy of the outgoing edges list.
35
+ def out_edges(filter=nil, &block)
36
+ @out_edges.filter(filter, &block)
37
+ end
38
+
39
+ # Returns all adjacent vertices
40
+ def adjacent(filter=nil, &block)
41
+ (in_adjacent(filter, &block) + out_adjacent(filter, &block)).uniq
42
+ end
43
+
44
+ # Returns back-adjacent vertices
45
+ def in_adjacent(filter=nil, &block)
46
+ @in_edges.source.filter(filter, &block)
47
+ end
48
+
49
+ # Returns forward-adjacent vertices
50
+ def out_adjacent(filter=nil, &block)
51
+ @out_edges.target.filter(filter, &block)
52
+ end
53
+
54
+
55
+ ### Pseudo-protected section ##########################################
56
+
57
+ # Adds an incoming edge
58
+ def add_in_edge(edge)
59
+ @in_edges << edge
60
+ end
61
+
62
+ # Removes an incoming edge
63
+ def remove_in_edge(edge)
64
+ @in_edges.delete(edge)
65
+ end
66
+
67
+ # Adds an outgoing edge
68
+ def add_out_edge(edge)
69
+ @out_edges << edge
70
+ end
71
+
72
+ # Removes an outgoing edge
73
+ def remove_out_edge(edge)
74
+ @out_edges.delete(edge)
75
+ end
76
+
77
+
78
+ ### Sort, Hash, etc. section ############################################
79
+
80
+ # Compares indexes
81
+ def <=>(other)
82
+ return nil unless Vertex===other and self.graph==other.graph
83
+ self.index <=> other.index
84
+ end
85
+
86
+
87
+ ### Export section ######################################################
88
+
89
+ # Returns a string representation
90
+ def to_s; "V#{index}" end
91
+
92
+ # Inspects the vertex
93
+ def inspect; "V#{index}" end
94
+
95
+ end # class Vertex
96
+
97
+ end
98
+ end
@@ -0,0 +1,38 @@
1
+ module Yargi
2
+
3
+ # A set of edges
4
+ class EdgeSet < ElementSet
5
+
6
+ ### Factory section #######################################################
7
+
8
+ # Creates a VertexSet instance using _elements_ varargs.
9
+ def self.[](*elements)
10
+ EdgeSet.new(elements)
11
+ end
12
+
13
+ ### Walking section #######################################################
14
+
15
+ # Returns a VertexSet with reachable vertices using the edges of this set.
16
+ def target
17
+ VertexSet.new(self.collect {|e| e.target}).uniq
18
+ end
19
+ alias :targets :target
20
+
21
+ # Returns a VertexSet with back-reachable vertices using the edges of this
22
+ # set.
23
+ def source
24
+ VertexSet.new(self.collect {|e| e.source}).uniq
25
+ end
26
+ alias :sources :source
27
+
28
+ ### Protected section #####################################################
29
+ protected
30
+
31
+ # Extends with EdgeSet instead of ElementSet
32
+ def extend_result(result)
33
+ EdgeSet.new(result)
34
+ end
35
+
36
+ end # module EdgeSet
37
+
38
+ end