jumoku 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,55 +1,18 @@
1
1
  module Jumoku
2
- # This module provides the basic routines needed to implement the specialized builders:
3
- # {TreeBuilder}, {BinaryTreeBuilder}, each of them streamlining {RawTreeBuilder}'s
4
- # behavior. Those implementations are under the control of the {TreeAPI}.
2
+ # This module provides the basic routines needed to implement the specialized
3
+ # builders: {UndirectedTreeBuilder} and {DirectedTreeBuilder}. Those
4
+ # implementations are under the control of the {TreeAPI}.
5
5
  #
6
- # A {RawTree} sticks to the standard definition of trees in Graph Theory: undirected,
7
- # connected, acyclic graphs. Using Plexus::UndirectedGraphBuilder as its backend,
8
- # {RawTreeBuilder} ensures the two remaining constraints are satisfied (connected and
9
- # acyclic). {RawTreeBuilder RawTree} offers limited functionalities, therefore the main
10
- # tree structure you'll likely to use is its extended version, {TreeBuilder Tree}. A
11
- # {Tree} share the same behavior but is a fully-fledged object with user-friendly public
12
- # methods built upon {RawTree}'s internals.
13
- #
14
- # Note that a node can be any Object. There is no "node type", therefore arguments which
15
- # are expected to be nodes are simply labelled as "`node`". A nice object type to us as
16
- # a node would be an OpenStruct or an
17
- # [OpenObject](http://facets.rubyforge.org/apidoc/api/more/classes/OpenObject.html)
18
- # (from the Facets library), as it turns nodes into versatile handlers.
19
- #
20
- # This builder defines a few methods not required by the API so as to maintain consistency
21
- # in the DSL.
22
- module RawTreeBuilder
23
- include Plexus::UndirectedGraphBuilder
24
-
25
- # This method is called by the specialized implementations
26
- # upon tree creation.
27
- #
28
- # Initialization parameters can include:
29
- #
30
- # * an array of branches to add
31
- # * one or several trees to copy (will be merged if multiple)
32
- #
33
- # @param *params [Hash] the initialization parameters
34
- # @return [RawTree]
35
- def initialize(*params)
36
- class << self
37
- self
38
- end.module_eval do
39
- # Ensure the builder abides by its API requirements.
40
- include Jumoku::TreeAPI
6
+ module Shared
7
+ def self.included(base)
8
+ base.class_eval do
9
+ # Late aliasing as it references methods provided by Plexus modules.
10
+ alias has_node? has_vertex?
11
+ alias has_branch? has_edge?
12
+ include TreeAPI
41
13
  end
42
- super(*params) # Delegates to Plexus.
43
14
  end
44
15
 
45
- # Ensure trees are seen as undirected graphs.
46
- #
47
- # @return [Boolean] false
48
- def directed?
49
- return false
50
- end
51
- # FIXME: should be able to reach Plexus's method ?!
52
-
53
16
  # Adds the node to the tree.
54
17
  #
55
18
  # For convenience, you may pass a branch as the parameter,
@@ -64,7 +27,7 @@ module Jumoku
64
27
  def add_node! u, v = nil
65
28
  if nodes.empty?
66
29
  add_vertex! u
67
- elsif u.is_a? Jumoku::Branch
30
+ elsif u.is_a? _branch_type
68
31
  add_branch! u
69
32
  elsif not v.nil?
70
33
  add_branch! u, v
@@ -86,16 +49,17 @@ module Jumoku
86
49
  # @overload add_branch!(b)
87
50
  # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
88
51
  # @return [RawTree] self
52
+ #
89
53
  def add_branch! u, v = nil, l = nil
90
54
  if has_node? u and has_node? v
91
55
  unless has_branch? u, v
92
56
  # Ensure the acyclic constraint.
93
- raise RawTreeError, "Can't form a cycle within a tree."
57
+ raise ForbiddenCycle, "Can't form a cycle within a tree."
94
58
  end
95
59
  end
96
60
 
97
61
  # TODO: DRY this up.
98
- if u.is_a? Jumoku::Branch
62
+ if u.is_a? _branch_type
99
63
  v = u.target
100
64
  u = u.source
101
65
  end
@@ -120,11 +84,11 @@ module Jumoku
120
84
  # @param [node] u
121
85
  # @return [RawTree] self
122
86
  def remove_node!(u, force = false)
123
- if terminal? u or force
87
+ if force || terminal?(u)
124
88
  remove_vertex! u
125
89
  else
126
90
  # Ensure the connected constraint.
127
- raise RawTreeError, "Can't remove a non terminal node in a tree"
91
+ raise UndefinedNode, "Can't remove a non terminal node in a tree"
128
92
  end
129
93
  end
130
94
 
@@ -144,7 +108,7 @@ module Jumoku
144
108
  # @param [Branch] b
145
109
  # @return [RawTree] self
146
110
  def remove_branch! u, v = nil
147
- if u.is_a? Jumoku::Branch
111
+ if u.is_a? _branch_type
148
112
  v = u.target
149
113
  u = u.source
150
114
  end
@@ -186,14 +150,13 @@ module Jumoku
186
150
  # @return [Array(node)] only terminal nodes (empty array if no terminal nodes,
187
151
  # but should never happen in a tree).
188
152
  def terminal_nodes
189
- nodes.inject([]) do |t_nodes, node|
190
- t_nodes << node if terminal?(node)
191
- t_nodes # must return t_nodes explicitily because
192
- # (rubydoc Enumerable#inject) "at each step, memo is set to the value returned by the block"
193
- # (sets t_nodes to nil otherwise).
153
+ nodes.inject([]) do |terminals, node|
154
+ terminals << node if terminal?(node)
155
+ terminals
194
156
  end
195
157
  end
196
158
  alias boundaries terminal_nodes
159
+ alias leaves terminal_nodes
197
160
 
198
161
  # The branches of the tree in a 1D array.
199
162
  #
@@ -202,10 +165,6 @@ module Jumoku
202
165
  edges
203
166
  end
204
167
 
205
- # Aliasing.
206
- alias has_node? has_vertex?
207
- alias has_branch? has_edge?
208
-
209
168
  # Tree helpers.
210
169
 
211
170
  # Checks whether the tree is *really* a valid tree, that is if the
@@ -228,10 +187,11 @@ module Jumoku
228
187
  # root is always terminal, otherwise check whether degree is unitary
229
188
  nodes == [u] ? true : (degree(u) == 1)
230
189
  else
231
- raise RawTreeNodeError, "Not a node of this tree."
190
+ raise UndefinedNode, "Not a node of this tree."
232
191
  end
233
192
  end
234
193
  alias has_terminal_node? terminal?
194
+ alias leaf? terminal?
235
195
 
236
196
  # Is the tree empty?
237
197
  #
@@ -239,5 +199,20 @@ module Jumoku
239
199
  def empty?
240
200
  nodes.empty?
241
201
  end
202
+
203
+ private
204
+
205
+ # Do *not* call cloning method on a cloned tree, as it will trigger
206
+ # an infinite cloning loop.
207
+ #
208
+ # TODO: add support for a global CLONING flag (defaults: true). If set
209
+ # to false, _clone would just return self, so that #add_node would behave
210
+ # like #add_node! for instance.
211
+ #
212
+ # @return [Tree] cloned tree
213
+ #
214
+ def _clone
215
+ self.class.new(self)
216
+ end
242
217
  end
243
218
  end
@@ -1,336 +1,11 @@
1
1
  module Jumoku
2
- # This builder extends the cheap implementation {RawTreeBuilder}, which
3
- # purpose is to implement the {TreeAPI}. {TreeBuilder} provides extended
4
- # functionalities and acts as the main tree structure you may use, either
5
- # by mixing-in this module or by inheritance of the associated {Tree} class.
2
+ # This builder extends {RawUndirectedTreeBuilder} implementation.
6
3
  #
7
- # tree = Tree.new
4
+ # It provides an undirected tree which acts as a hierarchical structure,
5
+ # known as a tree.
8
6
  #
9
- # # or
10
- #
11
- # class MyTree < Tree
12
- # # your stuff
13
- # end
14
- # tree = MyTree.new
15
- #
16
- # # or
17
- #
18
- # class MyTree
19
- # include TreeBuilder
20
- # # your stuff
21
- # end
22
- # tree = MyTree.new
23
7
  module TreeBuilder
24
- include RawTreeBuilder
25
-
26
- def initialize(*params)
27
- super(*params)
28
- end
29
-
30
- # Non destructive version of {RawTreeBuilder#add_node!} (works on a copy of the tree).
31
- #
32
- # @overload add_node!(n)
33
- # @param [node] n
34
- # @overload add_node!(b)
35
- # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
36
- # @return [Tree] a modified copy of `self`
37
- # @see RawTreeBuilder#add_node!
38
- def add_node(n, l = nil)
39
- x = self.class.new(self)
40
- x.add_node!(n, l)
41
- end
42
-
43
- # Non destructive version {RawTreeBuilder#add_branch!} (works on a copy of the tree).
44
- #
45
- # @overload add_branch!(i, j, l = nil)
46
- # @param [node] i
47
- # @param [node] j
48
- # @param [Label] l
49
- # @overload add_branch!(b)
50
- # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
51
- # @return [Tree] a modified copy of `self`
52
- # @see RawTreeBuilder#add_branch!
53
- def add_branch(i, j = nil, l = nil)
54
- x = self.class.new(self)
55
- x.add_branch!(i, j, l)
56
- end
57
-
58
- # Non destructive version of {RawTreeBuilder#remove_node!} (works on a copy of the tree).
59
- #
60
- # @param [node] n
61
- # @return [Tree] a modified copy of `self`
62
- # @see RawTreeBuilder#remove_node!
63
- def remove_node(n)
64
- x = self.class.new(self)
65
- x.remove_node!(n)
66
- end
67
-
68
- # Non destructive version {RawTreeBuilder#remove_branch!} (works on a copy of the tree).
69
- #
70
- # You cannot remove non terminal branches as it would break the connectivity constraint
71
- # of the tree.
72
- #
73
- # @param [node] i
74
- # @param [node] j
75
- # @return [Tree] a modified copy of `self`
76
- # @see RawTreeBuilder#remove_branch!
77
- def remove_branch(i, j = nil, *params)
78
- x = self.class.new(self)
79
- x.remove_branch!(i, j)
80
- end
81
-
82
- # Adds all specified nodes to the node set.
83
- #
84
- # The nodes defines implicit branches, that is it is mandatory to provide
85
- # an odd number of connected nodes which do not form cycles. You may specify
86
- # Branch objects within the list, though.
87
- #
88
- # Valid usage:
89
- #
90
- # tree = Tree.new # an empty tree
91
- # tree.add_nodes!(1,2, 2,3, 3,4) # tree.nodes => [1, 2, 3, 4]
92
- # # branches are (1-2), (2-3), (3-4)
93
- # tree.add_nodes!(1,2, 2,3, Branch.new(3,4), 10,1)
94
- # tree.add_nodes! [1,2, 2,3, 3,4]
95
- #
96
- # Invalid usages:
97
- #
98
- # tree = Tree.new # an empty tree
99
- # tree.add_nodes!(1, 2, 3) # even number of nodes
100
- # tree.add_nodes!(1,2, 2,3, 3,1) # cycle
101
- # tree.add_nodes!(1,2, 4,5) # not connected
102
- #
103
- # A helper exist to make it easy to add nodes in a suite.
104
- # See {TreeBuilder#add_consecutive_nodes! add_consecutive_nodes!}.
105
- #
106
- # @param [#each] *a an Enumerable nodes set
107
- # @return [Tree] `self`
108
- def add_nodes!(*a)
109
- a = a.flatten.expand_branches!
110
- if a.size == 1 # This allows for using << to add the root as well,
111
- if empty? # so that you may write:
112
- add_node! a.only # Tree.new << :root << [:root, 1] << [1,2, 2,3, Branch.new(3,4)]...
113
- else
114
- raise RawTreeError, "Cannot add a node without a neighbour."
115
- end
116
- else
117
- a.expand_branches!.each_nodes_pair { |pair| add_branch! pair[0], pair[1] }
118
- end
119
- self
120
- end
121
- alias << add_nodes!
122
-
123
- # Same as {TreeBuilder#add_nodes! add_nodes!} but works on a copy of the receiver.
124
- #
125
- # @param [#each] *a an Enumerable nodes set
126
- # @return [Tree] a modified copy of `self`
127
- def add_nodes(*a)
128
- x = self.class.new(self)
129
- x.add_nodes!(*a)
130
- end
131
-
132
- # Allows for adding consecutive nodes, each nodes being connected to their previous and
133
- # next neighbours.
134
- #
135
- # You may pass {Branch branches} within the nodes.
136
- #
137
- # Tree.new.add_consecutive_nodes!(1, 2, 3, :four, Branch.new(:foo, "bar")).branches
138
- # # => (1=2, 2=3, 3=:four, :four=:foo, :foo="bar")
139
- #
140
- # @param [Array(nodes)] *a flat array of unique nodes
141
- # @return [Tree] `self`
142
- def add_consecutive_nodes!(*a)
143
- # FIXME: really this may not be as efficient as it could be.
144
- # We may get rid of nodes duplication (no expand_branches!)
145
- # and add nodes by pair, shifting one node at a time from the list.
146
- add_nodes!(a.expand_branches!.create_nodes_pairs_list)
147
- self
148
- end
149
-
150
- # Same as {TreeBuilder#add_consecutive_nodes! add_consecutive_nodes!} but
151
- # works on a copy of the receiver.
152
- #
153
- # @param [Array(nodes)] *a flat array of unique nodes
154
- # @return [Tree] a modified copy of `self`
155
- def add_consecutive_nodes(*a)
156
- x = self.class.new(self)
157
- x.add_consecutive_nodes!(*a)
158
- end
159
-
160
- # Adds all branches mentionned in the specified Enumerable to the branch set.
161
- #
162
- # Elements of the Enumerable can be either two-element arrays or instances of
163
- # {Branch}.
164
- #
165
- # @param [#each] *a an Enumerable branches set
166
- # @return [Tree] `self`
167
- def add_branches!(*a)
168
- a.expand_branches!.each_nodes_pair { |pair| add_branch! pair[0], pair[1] }
169
- self
170
- end
171
-
172
- # Same as {TreeBuilder#add_branches! add_branches!} but works on a copy of the receiver.
173
- #
174
- # @param [#each] *a an Enumerable branches set
175
- # @return [Tree] a modified copy of `self`
176
- def add_branches(*a)
177
- x = self.class.new(self)
178
- x.add_branches!(*a)
179
- end
180
-
181
- # Removes all nodes mentionned in the specified Enumerable from the tree.
182
- #
183
- # The process relies on {RawTreeBuilder#remove_node! remove_node!}.
184
- #
185
- # @param [#each] *a an Enumerable nodes set
186
- # @return [Tree] `self`
187
- def remove_nodes!(*a)
188
- a.flatten.each { |v| remove_node! v }
189
- self
190
- end
191
- alias delete_nodes! remove_nodes!
192
-
193
- # Same as {TreeBuilder#remove_nodes! remove_nodes!} but works on a copy of the receiver.
194
- #
195
- # @param [#each] *a a node Enumerable set
196
- # @return [Tree] a modified copy of `self`
197
- def remove_nodes(*a)
198
- x = self.class.new(self)
199
- x.remove_nodes!(*a)
200
- end
201
- alias delete_nodes remove_nodes
202
-
203
- # Removes all branches mentionned in the specified Enumerable from the tree.
204
- #
205
- # @param [#each] *a an Enumerable branches set
206
- # @return [Tree] `self`
207
- def remove_branches!(*a)
208
- a.expand_branches!.each_nodes_pair { |pair| remove_branch! pair[0], pair[1] }
209
- self
210
- end
211
- alias delete_branches! remove_branches!
212
-
213
- # Same as {TreeBuilder#remove_branches! remove_branches!} but works on a copy of the receiver.
214
- #
215
- # @param [#each] *a an Enumerable branches set
216
- # @return [Tree] a modified copy of `self`
217
- # @see RawTreeBuilder#remove_branch!
218
- def remove_branches(*a)
219
- x = self.class.new(self)
220
- x.remove_branches!(*a)
221
- end
222
- alias delete_branches remove_branches
223
-
224
- # Returns true if the specified node belongs to the tree.
225
- #
226
- # This is a default implementation that is of O(n) average complexity.
227
- # If a subclass uses a hash to store nodes, then this can be
228
- # made into an O(1) average complexity operation.
229
- #
230
- # @param [node] n
231
- # @return [Boolean]
232
- def node?(n)
233
- nodes.include?(n)
234
- end
235
- alias has_node? node?
236
-
237
- # Returns true if all specified nodes belong to the tree.
238
- #
239
- # @param [nodes]
240
- # @return [Boolean]
241
- def nodes?(*maybe_nodes)
242
- maybe_nodes.all? { |node| nodes.include? node }
243
- end
244
- alias has_nodes? nodes?
245
-
246
- # Returns true if any of the specified nodes belongs to the tree.
247
- #
248
- # @param [nodes]
249
- # @return [Boolean]
250
- def nodes_among?(*maybe_nodes)
251
- maybe_nodes.any? { |node| nodes.include? node }
252
- end
253
- alias has_nodes_among? nodes_among?
254
- alias has_node_among? nodes_among?
255
- # FIXME: these helpers could be backported into Plexus.
256
-
257
- # Returns true if i or (i,j) is a {Branch branch} of the tree.
258
- #
259
- # @overload branch?(a)
260
- # @param [Branch] a
261
- # @overload branch?(i, j)
262
- # @param [node] i
263
- # @param [node] j
264
- # @return [Boolean]
265
- def branch?(*args)
266
- branches.include?(edge_convert(*args))
267
- end
268
- alias has_branch? branch?
269
-
270
- # Returns true if the specified branches are a subset of or match exactly the
271
- # branches set of the tree (no ordering criterion).
272
- #
273
- # Labels are not part of the matching constraint: only connected nodes
274
- # matter in defining branch equality.
275
- #
276
- # @param [*Branch, *nodes] *maybe_branches a list of branches, either as Branch
277
- # objects or as nodes pairs
278
- # @return [Boolean]
279
- def branches?(*maybe_branches)
280
- list = maybe_branches.create_branches_list
281
- all = true
282
-
283
- # Branch objects are really Edge objects within Plexus, therefore
284
- # cannot rely on #eql? to compare those structures and must drop
285
- # down to the attributes.
286
- list.each do |e| # Jumoku::Branch structs
287
- all = branches.any? do |b| # Plexus::Edge structs
288
- (b[:source] == e[:source]) and (b[:target] == e[:target])
289
- end
290
- end
291
- all
292
- end
293
- alias has_branches? branches?
294
-
295
- # Returns true if actual branches are included in the specified set of branches
296
- # (no ordering criterion).
297
- #
298
- # Labels are not part of the matching constraint: only connected nodes
299
- # matter in defining equality.
300
- #
301
- # @param [*Branch, *nodes] *maybe_branches a list of branches, either as Branch
302
- # objects or as nodes pairs
303
- # @return [Boolean]
304
- def branches_among?(*maybe_branches)
305
- list = maybe_branches.create_branches_list
306
- all = true
307
-
308
- # Branch objects are really Edge objects within Plexus, therefore
309
- # cannot rely on #eql? to compare those structures and must drop
310
- # down to the attributes.
311
- branches.each do |e| # Plexus::Edge structs
312
- all = list.any? do |b| # Jumoku::Branch structs
313
- (b[:source] == e[:source]) and (b[:target] == e[:target])
314
- end
315
- end
316
- all
317
- end
318
- alias has_branches_among? branches_among?
319
-
320
- # Number of nodes.
321
- #
322
- # @return [Integer]
323
- def num_nodes
324
- nodes.size
325
- end
326
- alias number_of_nodes num_nodes
327
-
328
- # Number of branches.
329
- #
330
- # @return [Integer]
331
- def num_branches
332
- branches.size
333
- end
334
- alias number_of_branches num_branches
8
+ include RawUndirectedTreeBuilder
9
+ include Extended
335
10
  end
336
11
  end