yargi 0.1.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.
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