jumoku 0.1.3 → 0.2.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.
@@ -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