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.
@@ -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