yargi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,166 @@
1
+ require 'delegate'
2
+
3
+ module Yargi
4
+
5
+ # Main module of VertexSet and EdgeSet
6
+ class ElementSet < Array
7
+
8
+ ### Factory section #######################################################
9
+
10
+ # Creates a ElementSet instance using _elements_ varargs.
11
+ def self.[](*elements)
12
+ ElementSet.new(elements)
13
+ end
14
+
15
+
16
+ ### Array handling ########################################################
17
+
18
+ # Same as Array.dup
19
+ def dup() # :nodoc: #
20
+ extend_result(super)
21
+ end
22
+
23
+ # See Array.compact
24
+ def compact() # :nodoc: #
25
+ extend_result(super)
26
+ end
27
+
28
+ # See Array.flatten
29
+ def flatten() # :nodoc: #
30
+ extend_result(super)
31
+ end
32
+
33
+ # See Array.reverse
34
+ def reverse() # :nodoc: #
35
+ extend_result(super)
36
+ end
37
+
38
+ # See Array.uniq
39
+ def uniq() # :nodoc: #
40
+ extend_result(super)
41
+ end
42
+
43
+ # See Array.sort
44
+ def sort(&block) # :nodoc: #
45
+ extend_result(super(&block))
46
+ end
47
+
48
+ # See Array.concat
49
+ def concat(other) # :nodoc: #
50
+ extend_result(super(other))
51
+ end
52
+
53
+ # See Array.[]
54
+ def [](*args) # :nodoc: #
55
+ result = super(*args)
56
+ Array===result ? extend_result(result) : result
57
+ end
58
+
59
+ # See Array.+
60
+ def +(right) # :nodoc: #
61
+ extend_result(super(right))
62
+ end
63
+
64
+ # See Array.-
65
+ def -(right) # :nodoc: #
66
+ extend_result(super(right))
67
+ end
68
+
69
+
70
+ ### Enumerable handling ###################################################
71
+
72
+ # See Enumerable.each_cons
73
+ def each_cons(n) # :nodoc: #
74
+ super(n) {|c| yield extend_result(c)}
75
+ end
76
+
77
+ # See Enumerable.each_slice
78
+ def each_slice(n) # :nodoc: #
79
+ super(n) {|c| yield extend_result(c)}
80
+ end
81
+
82
+ # See Enumerable.select
83
+ def select(&block) # :nodoc: #
84
+ extend_result(super(&block))
85
+ end
86
+
87
+ # See Enumerable.find_all
88
+ def find_all(&block) # :nodoc: #
89
+ extend_result(super(&block))
90
+ end
91
+
92
+ # See Enumerable.grep
93
+ def grep(pattern, &block) # :nodoc: #
94
+ greped = super(pattern, &block)
95
+ block_given? ? greped : extend_result(greped)
96
+ end
97
+
98
+ # See Enumerable.reject
99
+ def partition(&block) # :nodoc: #
100
+ p = super(&block)
101
+ [extend_result(p[0]), extend_result(p[1])]
102
+ end
103
+
104
+ # See Enumerable.reject
105
+ def reject(&block) # :nodoc: #
106
+ extend_result(super(&block))
107
+ end
108
+
109
+
110
+ ### Markable handling #####################################################
111
+
112
+ # Fired to each element of the group.
113
+ def tag(*modules)
114
+ self.each {|elm| elm.tag(*modules)}
115
+ end
116
+
117
+ # Collects result of get_mark invocation on each element of the
118
+ # group and returns it as an array.
119
+ def get_mark(key)
120
+ self.collect {|elm| elm.get_mark(key)}
121
+ end
122
+
123
+ # Fired to each element of the group. Values are duplicated by default.
124
+ # Put dup to false to avoid this behavior.
125
+ def set_mark(key, value, dup=true)
126
+ self.each {|elm| elm.set_mark(key, (dup and not(Symbol===value)) ? value.dup : value)}
127
+ end
128
+
129
+ # When _marks_ is provided, the invocation is fired to all group
130
+ # elements. When a block is given, it is called on each element,
131
+ # passing it as argument. If the block returns a hash, that hash
132
+ # is installed as marks on the iterated element.
133
+ # The two usages (_marks_ and block) can be used conjointly.
134
+ def add_marks(marks=nil)
135
+ self.each {|elm| elm.add_marks(marks)} if marks
136
+ if block_given?
137
+ self.each do |elm|
138
+ hash = yield elm
139
+ elm.add_marks(hash) if hash
140
+ end
141
+ end
142
+ end
143
+ alias :merge_marks :add_marks
144
+
145
+
146
+ ### Query handling ########################################################
147
+
148
+ # Filters this set with a 'predicate and block' predicate
149
+ # (see Yargi::Predicate)
150
+ def filter(predicate=nil, &block)
151
+ pred = Yargi::Predicate.to_predicate(predicate, &block)
152
+ extend_result(self.select{|e| pred===e})
153
+ end
154
+
155
+ ### Protected section #####################################################
156
+ protected
157
+
158
+ # Extends a resulting array with the module. This method is intended
159
+ # to be overrided by specialization of this module.
160
+ def extend_result(result)
161
+ ElementSet.new(result)
162
+ end
163
+
164
+ end # class ElementSet
165
+
166
+ end
@@ -0,0 +1,59 @@
1
+ module Yargi
2
+
3
+ #
4
+ # Allows users to put its own marks on graph elements. A Hash-like API for setting
5
+ # and getting these marks is provided through <tt>[]</tt> and <tt>[]=</tt>. When a
6
+ # Symbol object is used as a mark key (and provided it does'nt lead to a name
7
+ # collision), accessors are automatically defined as singleton methods (without
8
+ # creating an instance variable however).
9
+ #
10
+ module Markable
11
+
12
+ # Tag this element with some modules
13
+ def tag(*modules)
14
+ modules.each {|mod| self.extend(mod)}
15
+ end
16
+
17
+ # Returns the mark value installed under _key_. Returns nil if no such mark.
18
+ def get_mark(key)
19
+ @marks ? @marks[key] : nil;
20
+ end
21
+ alias :[] :get_mark
22
+
23
+ # Sets a key/value pair as a mark. Overrides previous mark value if _key_ is
24
+ # already in used. Automatically creates accessors if _key_ is a Symbol and such
25
+ # methods do not already exists.
26
+ def set_mark(key, value)
27
+ @marks = {} unless @marks
28
+ @marks[key] = value
29
+ if Symbol===key and not(self.respond_to?(key) or self.respond_to?("#{key}=".to_sym))
30
+ instance_eval %Q{
31
+ class << self
32
+ def #{key}() @marks[:#{key}]; end
33
+ def #{key}=(value) @marks[:#{key}]=value; end
34
+ end
35
+ }
36
+ end
37
+ end
38
+ alias :[]= :set_mark
39
+
40
+ # Add all marks provided by a Hash instance _marks_.
41
+ def add_marks(marks)
42
+ marks.each_pair {|k,v| self[k]=v}
43
+ end
44
+ alias :merge_marks :add_marks
45
+
46
+ # Converts this Markable to a Hash. When _nonil_ is true, nil mark values
47
+ # do not lead to hash entries.
48
+ def to_h(nonil=true)
49
+ return {} unless @marks
50
+ marks = @marks.dup
51
+ if nonil
52
+ marks.delete_if {|k,v| v.nil?}
53
+ end
54
+ marks
55
+ end
56
+
57
+ end # module Markable
58
+
59
+ end
@@ -0,0 +1,229 @@
1
+ module Yargi
2
+
3
+ #
4
+ # Predicate for graph elements.
5
+ #
6
+ # The following automatic conversions apply on Ruby standard classes
7
+ # when using predicates (see to_predicate in particular):
8
+ # [Predicate] itself
9
+ # [Module] TagPredicate
10
+ # [Proc] LambdaPredicate
11
+ # [TrueClass, NilClass] TruePredicate
12
+ # [FalseClass] FalsePredicate
13
+ # [otherwise] an ArgumentError is raised.
14
+ #
15
+ class Predicate
16
+
17
+ class << self
18
+ # Builds a 'what and block' predicate, according the automatic conversions
19
+ # descibed above.
20
+ def to_predicate(what=nil, &block)
21
+ (what, block = block, nil) if what.nil?
22
+ p = case what
23
+ when Predicate
24
+ what
25
+ when Module
26
+ TagPredicate.new(what)
27
+ when Proc
28
+ LambdaPredicate.new(&what)
29
+ when TrueClass, NilClass
30
+ ALL
31
+ when FalseClass
32
+ NONE
33
+ else
34
+ raise ArgumentError, "Unable to convert #{what} to a predicate"
35
+ end
36
+ if block
37
+ p & LambdaPredicate.new(&block)
38
+ else
39
+ p
40
+ end
41
+ end
42
+ end
43
+
44
+ # Builds a 'self and right' predicate
45
+ def &(right)
46
+ AndPredicate.new(*[self, Predicate.to_predicate(right)])
47
+ end
48
+
49
+ # Builds a 'self or right' predicate
50
+ def |(right)
51
+ OrPredicate.new(*[self, Predicate.to_predicate(right)])
52
+ end
53
+
54
+ # Builds a 'not(self)' predicate
55
+ def not()
56
+ NotPredicate.new(self)
57
+ end
58
+
59
+ end # class Predicate
60
+
61
+ # Decorates _true_ as a predicate
62
+ class TruePredicate < Predicate
63
+
64
+ # Always returns true
65
+ def ===(elm)
66
+ true
67
+ end
68
+
69
+ # Returns 'true'
70
+ def inspect
71
+ "true"
72
+ end
73
+
74
+ end # class TruePredicate
75
+
76
+ # Decorates _false_ as a predicate
77
+ class FalsePredicate < Predicate
78
+
79
+ # Always returns false
80
+ def ===(elm)
81
+ false
82
+ end
83
+
84
+ # Returns 'false'
85
+ def inspect
86
+ "false"
87
+ end
88
+
89
+ end # class TruePredicate
90
+
91
+ # Decorates a module instance as a predicate
92
+ class TagPredicate < Predicate
93
+
94
+ # Creates a Tag predicate
95
+ def initialize(mod)
96
+ raise ArgumentError, "Module expected, #{mod} received" unless Module===mod
97
+ @mod = mod
98
+ end
99
+
100
+ # Predicate implementation
101
+ def ===(elm)
102
+ @mod===elm
103
+ end
104
+
105
+ # Helps debugging predicates
106
+ def inspect
107
+ "is_a?(#{@mod})"
108
+ end
109
+
110
+ end # class TagPredicate
111
+
112
+ # Decorates a block as a predicate
113
+ class LambdaPredicate < Predicate
114
+
115
+ # Creates a Tag predicate
116
+ def initialize(&block)
117
+ raise ArgumentError, "Block of arity 1 expected" unless (block and block.arity==1)
118
+ @block = block
119
+ end
120
+
121
+ # Predicate implementation
122
+ def ===(elm)
123
+ @block.call(elm)
124
+ end
125
+
126
+ # Helps debugging predicates
127
+ def inspect
128
+ "lambda{...}"
129
+ end
130
+
131
+ end # class LambdaPredicate
132
+
133
+ # Negates another predicate
134
+ class NotPredicate < Predicate
135
+
136
+ # Creates a 'not(negated)' predicate
137
+ def initialize(negated)
138
+ @negated = Predicate.to_predicate(negated)
139
+ end
140
+
141
+ # Predicate implementation
142
+ def ===(elm)
143
+ not(@negated === elm)
144
+ end
145
+
146
+ # Helps debugging predicates
147
+ def inspect
148
+ "not(#{@negated})"
149
+ end
150
+
151
+ end # class NotPredicate
152
+
153
+ # Conjunction predicate
154
+ class AndPredicate < Predicate
155
+
156
+ # Builds a AND predicate
157
+ def initialize(*anded)
158
+ raise ArgumentError, "Predicates expected" unless anded.all?{|p| Predicate===p}
159
+ @anded = anded
160
+ end
161
+
162
+ # Predicate implementation
163
+ def ===(elm)
164
+ @anded.all?{|p| p===elm}
165
+ end
166
+
167
+ # Pushes _right_ in the anded array
168
+ def &(right)
169
+ @anded << Predicate.to_predicate(right)
170
+ self
171
+ end
172
+
173
+ # Helps debugging predicates
174
+ def inspect
175
+ @anded.inject('') do |memo,p|
176
+ if memo.empty?
177
+ p.inspect
178
+ else
179
+ "#{memo} and #{p.inspect}"
180
+ end
181
+ end
182
+ end
183
+
184
+ end # class AndPredicate
185
+
186
+ # Disjunction predicate
187
+ class OrPredicate < Predicate
188
+
189
+ # Builds a OR predicate
190
+ def initialize(*ored)
191
+ raise ArgumentError, "Predicates expected" unless ored.all?{|p| Predicate===p}
192
+ @ored = ored
193
+ end
194
+
195
+ # Predicate implementation
196
+ def ===(elm)
197
+ @ored.any?{|p| p===elm}
198
+ end
199
+
200
+ # Pushes _right_ in the ored array
201
+ def |(right)
202
+ @ored << Predicate.to_predicate(right)
203
+ self
204
+ end
205
+
206
+ # Helps debugging predicates
207
+ def inspect
208
+ @ored.inject('') do |memo,p|
209
+ if memo.empty?
210
+ p.inspect
211
+ else
212
+ "#{memo} or #{p.inspect}"
213
+ end
214
+ end
215
+ end
216
+
217
+ end # class OrPredicate
218
+
219
+ class Predicate
220
+
221
+ # Predicates that always return true
222
+ ALL = TruePredicate.new
223
+
224
+ # Predicates that always return false
225
+ NONE = FalsePredicate.new
226
+
227
+ end # class Predicate
228
+
229
+ end
@@ -0,0 +1,56 @@
1
+ module Yargi
2
+
3
+ # A set of vertices
4
+ class VertexSet < ElementSet
5
+
6
+ ### Factory section #######################################################
7
+
8
+ # Creates a VertexSet instance using _elements_ varargs.
9
+ def self.[](*elements)
10
+ VertexSet.new(elements)
11
+ end
12
+
13
+
14
+ ### Walking section #######################################################
15
+
16
+ # Returns incoming edges of all vertices of this set
17
+ def in_edges(filter=nil, &block)
18
+ r = self.collect {|v| v.in_edges(filter, &block) }
19
+ EdgeSet.new(r).flatten.uniq
20
+ end
21
+
22
+ # Returns all back-adjacent vertices reachable from this set
23
+ def in_adjacent(filter=nil, &block)
24
+ r = self.collect {|v| v.in_adjacent(filter, &block) }
25
+ VertexSet.new(r).flatten.uniq
26
+ end
27
+
28
+ # Returns all outgoing edges of all vertices of this set
29
+ def out_edges(filter=nil, &block)
30
+ r = self.collect {|v| v.out_edges(filter, &block) }
31
+ EdgeSet.new(r).flatten.uniq
32
+ end
33
+
34
+ # Returns all forward-adjacent vertices reachable from this set
35
+ def out_adjacent(filter=nil, &block)
36
+ r = self.collect {|v| v.out_adjacent(filter, &block) }
37
+ VertexSet.new(r).flatten.uniq
38
+ end
39
+
40
+ # Returns all adjacent vertices reachable from this set
41
+ def adjacent(filter=nil, &block)
42
+ (in_adjacent(filter, &block)+out_adjacent(filter, &block)).uniq
43
+ end
44
+
45
+
46
+ ### Protected section #####################################################
47
+ protected
48
+
49
+ # Extends with VertexSet instead of ElementSet
50
+ def extend_result(result)
51
+ VertexSet.new(result)
52
+ end
53
+
54
+ end # module VertexSet
55
+
56
+ end
data/lib/yargi.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'yargi/predicate'
2
+
3
+ module Yargi
4
+
5
+ # Current Yargi version
6
+ VERSION = "0.1.0".freeze
7
+
8
+ # When _what_ is not nil, converts it to a predicate (typically a module).
9
+ # Otherwise, a block is expected, which is converted to a LambdaPredicate.
10
+ # Otherwise, return ALL.
11
+ def self.predicate(what=nil, &block)
12
+ Predicate.to_predicate(what, &block)
13
+ end
14
+
15
+ # Predicates that always return true
16
+ ALL = Yargi::Predicate.to_predicate(true)
17
+
18
+ # Predicates that always return false
19
+ NONE = Yargi::Predicate.to_predicate(false)
20
+
21
+ end
22
+
23
+ require 'yargi/markable'
24
+ require 'yargi/digraph'
25
+ require 'yargi/digraph_vertex'
26
+ require 'yargi/digraph_edge'
27
+ require 'yargi/element_set'
28
+ require 'yargi/vertex_set'
29
+ require 'yargi/edge_set'
30
+
data/test/test_all.rb ADDED
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__),'..', 'lib'))
2
+ require 'yargi'
3
+ require 'test/unit'
4
+ test_files = Dir[File.join(File.dirname(__FILE__), '**/*_test.rb')]
5
+ test_files.each { |file|
6
+ require(file)
7
+ }
8
+
@@ -0,0 +1,33 @@
1
+ digraph G {
2
+ graph[]
3
+ V0 [shape="circle"label=""]
4
+ V1 [shape="circle"label=""]
5
+ V2 [shape="circle"label=""]
6
+ V3 [shape="circle"label=""]
7
+ V4 [shape="circle"label=""]
8
+ V0 -> V0 [label="From 0 to 0"]
9
+ V0 -> V1 [label="From 0 to 1"]
10
+ V0 -> V2 [label="From 0 to 2"]
11
+ V0 -> V3 [label="From 0 to 3"]
12
+ V0 -> V4 [label="From 0 to 4"]
13
+ V1 -> V0 [label="From 1 to 0"]
14
+ V1 -> V1 [label="From 1 to 1"]
15
+ V1 -> V2 [label="From 1 to 2"]
16
+ V1 -> V3 [label="From 1 to 3"]
17
+ V1 -> V4 [label="From 1 to 4"]
18
+ V2 -> V0 [label="From 2 to 0"]
19
+ V2 -> V1 [label="From 2 to 1"]
20
+ V2 -> V2 [label="From 2 to 2"]
21
+ V2 -> V3 [label="From 2 to 3"]
22
+ V2 -> V4 [label="From 2 to 4"]
23
+ V3 -> V0 [label="From 3 to 0"]
24
+ V3 -> V1 [label="From 3 to 1"]
25
+ V3 -> V2 [label="From 3 to 2"]
26
+ V3 -> V3 [label="From 3 to 3"]
27
+ V3 -> V4 [label="From 3 to 4"]
28
+ V4 -> V0 [label="From 4 to 0"]
29
+ V4 -> V1 [label="From 4 to 1"]
30
+ V4 -> V2 [label="From 4 to 2"]
31
+ V4 -> V3 [label="From 4 to 3"]
32
+ V4 -> V4 [label="From 4 to 4"]
33
+ }
Binary file
@@ -0,0 +1,96 @@
1
+ require 'test/unit'
2
+ require 'yargi'
3
+
4
+ module Yargi
5
+
6
+ # Tests all set-based features on graphs
7
+ class DigraphSetFeaturesTest < Test::Unit::TestCase
8
+
9
+ module Source; end
10
+ module Sink; end
11
+ module Additional; end
12
+
13
+ # Installs the souce-sink graph example under @graph
14
+ def setup
15
+ @graph = Yargi::Digraph.new
16
+ sources = @graph.add_n_vertices(5, Source)
17
+ sinks = @graph.add_n_vertices(5, Sink)
18
+ @graph.connect(sources, sinks)
19
+ end
20
+
21
+ def test_tag
22
+ @graph.vertices{|v| Source===v}.tag(Additional)
23
+ @graph.vertices.each {|v| assert_equal((Source===v), (Additional===v))}
24
+ assert @graph.vertices{|v| Source===v}.all?{|v|Additional===v}
25
+ assert @graph.vertices{|v| Sink===v}.all?{|v|not(Additional===v)}
26
+ end
27
+
28
+ def test_set_and_get_mark
29
+ @graph.vertices{|v| Source===v}.set_mark(:kind, :source)
30
+ @graph.vertices{|v| Sink===v}.set_mark(:kind, :sink)
31
+ @graph.vertices.each do |v|
32
+ if Source===v
33
+ assert_equal :source, v.get_mark(:kind)
34
+ else
35
+ assert_equal :sink, v.get_mark(:kind)
36
+ end
37
+ end
38
+ end
39
+
40
+ def test_add_marks
41
+ @graph.vertices{|v| Source===v}.add_marks(:kind => :source, :priority => 1.0)
42
+ @graph.vertices.each do |v|
43
+ if Source===v
44
+ assert_equal :source, v.kind
45
+ assert_equal 1.0, v.priority
46
+ end
47
+ end
48
+ end
49
+
50
+ def test_add_marks_with_block
51
+ @graph.vertices{|v| Source===v}.add_marks do |v|
52
+ v.set_mark(:test, true)
53
+ {:kind => :source, :priority => 1.0}
54
+ end
55
+ @graph.vertices{|v| Source===v}.all? do |v|
56
+ assert v.test==true
57
+ assert_equal :source, v.kind
58
+ assert_equal 1.0, v.priority
59
+ end
60
+ end
61
+
62
+ def test_add_marks_with_both
63
+ @graph.vertices{|v| Source===v}.add_marks(:test2 => false) do |v|
64
+ v.set_mark(:test, true)
65
+ {:kind => :source, :priority => 1.0}
66
+ end
67
+ @graph.vertices{|v| Source===v}.all? do |v|
68
+ assert v.test2==false
69
+ assert v.test==true
70
+ assert_equal :source, v.kind
71
+ assert_equal 1.0, v.priority
72
+ end
73
+ end
74
+
75
+ def test_to_dot
76
+ @graph.vertices{|v|Source===v}.add_marks(:label => '', :shape => 'diamond', :fixedsize => true, :width => 0.5)
77
+ @graph.vertices{|v|Sink===v}.add_marks(:label => '', :shape => 'doublecircle', :fixedsize => true, :width => 0.5)
78
+ @graph.edges.add_marks do |e|
79
+ {:label => "from #{e.source.index} to #{e.target.index}"}
80
+ end
81
+ dir = File.expand_path(File.dirname(__FILE__))
82
+ dotfile = File.join(dir,"source-sink.dot")
83
+ gitfile = File.join(dir,"source-sink.gif")
84
+ File.open(dotfile, 'w') {|f| f << @graph.to_dot}
85
+ begin
86
+ `dot -Tgif -o #{gitfile} #{dotfile}`
87
+ rescue => ex
88
+ $STDERR << "dot test failed, probably not installed\n#{ex.message}"
89
+ end
90
+ end
91
+
92
+
93
+
94
+ end # class DigraphSetFeaturesSet
95
+
96
+ end