yargi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENCE +25 -0
- data/README +79 -0
- data/lib/yargi/digraph.rb +287 -0
- data/lib/yargi/digraph_edge.rb +81 -0
- data/lib/yargi/digraph_vertex.rb +98 -0
- data/lib/yargi/edge_set.rb +38 -0
- data/lib/yargi/element_set.rb +166 -0
- data/lib/yargi/markable.rb +59 -0
- data/lib/yargi/predicate.rb +229 -0
- data/lib/yargi/vertex_set.rb +56 -0
- data/lib/yargi.rb +30 -0
- data/test/test_all.rb +8 -0
- data/test/yargi/README-example.dot +33 -0
- data/test/yargi/README-example.gif +0 -0
- data/test/yargi/digraph_set_features_test.rb +96 -0
- data/test/yargi/digraph_test.rb +360 -0
- data/test/yargi/digraph_vertex_test.rb +61 -0
- data/test/yargi/documentation_test.rb +44 -0
- data/test/yargi/element_set_test.rb +17 -0
- data/test/yargi/hypotheses_test.rb +30 -0
- data/test/yargi/markable_test.rb +56 -0
- data/test/yargi/predicate_test.rb +90 -0
- data/test/yargi/source-sink.dot +38 -0
- data/test/yargi/source-sink.gif +0 -0
- data/test/yargi/star.dot +144 -0
- data/test/yargi/vertex_set_test.rb +43 -0
- metadata +85 -0
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
|