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 +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
|