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.
data/README.md CHANGED
@@ -1,5 +1,3 @@
1
- ![Texte alternatif](http://img547.imageshack.us/img547/2932/logoJumoku.png "Jumoku logo")
2
-
3
1
  **Build and manipulate tree structures in Ruby.**
4
2
 
5
3
  ## Synopsis
data/lib/jumoku.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  libpath = File.expand_path(File.dirname(__FILE__))
2
2
  $:.unshift File.dirname(__FILE__) unless $:.include?(File.dirname(__FILE__)) || $:.include?(libpath)
3
3
  $: << libpath + '/../vendor/git/plexus/lib'
4
- puts $:.inspect
5
4
 
6
5
  require 'pathname'
7
6
  require 'pp'
@@ -9,15 +8,44 @@ require 'active_support'
9
8
  require 'facets'
10
9
  require 'plexus'
11
10
 
11
+ # Jumoku provides you with several modules and classes to build and manipulate
12
+ # tree graphs. Two basic implementations are available: undirected trees
13
+ # ({RawUndirectedTree}) and directed trees ({RawDirectedTree}).
14
+ #
15
+ # {Tree} is derived from the undirected flavour and sticks to the mathematical
16
+ # tree definition. {Arborescence} is derived from the directed flavour and is
17
+ # likely to be used as the basis to modelize hierarchy structures, such as a
18
+ # family tree, a file browser…
19
+ #
20
+ # Note that a node can be any Object. There is no "node type", therefore
21
+ # arguments which re expected to be nodes are simply labelled as "`node`"
22
+ # within this documentation. A nice object type to use as a node may be an
23
+ # OpenStruct or an [OpenObject](http://facets.rubyforge.org/apidoc/api/more/classes/OpenObject.html)
24
+ # (from the Facets library), both turning nodes into versatile handlers.
25
+ #
12
26
  module Jumoku
13
- # jumoku internals: graph builders and additionnal behaviors
14
- autoload :TreeAPI, 'jumoku/tree_api'
15
- autoload :RawTreeBuilder, 'jumoku/builders/raw_tree'
16
- autoload :TreeBuilder, 'jumoku/builders/tree'
17
- autoload :RawTree, 'jumoku/classes/tree_classes'
18
- autoload :Tree, 'jumoku/classes/tree_classes'
19
- autoload :Branch, 'jumoku/support/branch'
27
+ # API, core implementations
28
+ autoload :TreeAPI, 'jumoku/tree_api'
29
+ autoload :Shared, 'jumoku/builders/shared'
30
+ autoload :Extended, 'jumoku/builders/extended'
20
31
 
32
+ # branch types
33
+ autoload :UndirectedBranch, 'jumoku/support/branch'
34
+ autoload :DirectedBranch, 'jumoku/support/branch'
35
+
36
+ # tree builders
37
+ autoload :RawUndirectedTreeBuilder, 'jumoku/builders/raw_undirected_tree'
38
+ autoload :RawDirectedTreeBuilder, 'jumoku/builders/raw_directed_tree'
39
+ autoload :TreeBuilder, 'jumoku/builders/tree'
40
+ autoload :ArborescenceBuilder, 'jumoku/builders/arborescence'
41
+
42
+ # tree classes
43
+ autoload :RawDirectedTree, 'jumoku/classes/tree_classes'
44
+ autoload :RawUndirectedTree, 'jumoku/classes/tree_classes'
45
+ autoload :Tree, 'jumoku/classes/tree_classes'
46
+ autoload :Arborescence, 'jumoku/classes/tree_classes'
47
+
48
+ # support
21
49
  require 'jumoku/support/support'
22
50
  require 'jumoku/ext/ext'
23
51
  end
@@ -0,0 +1,11 @@
1
+ module Jumoku
2
+ # This builder extends {RawDirectedTreeBuilder} implementation.
3
+ #
4
+ # It provides a directed tree which acts as a hierarchical structure, known
5
+ # as an arborescence.
6
+ #
7
+ module ArborescenceBuilder
8
+ include RawDirectedTreeBuilder
9
+ include Extended
10
+ end
11
+ end
@@ -0,0 +1,327 @@
1
+ module Jumoku
2
+ # This module provides methods extending the core TreeAPI. Most of those
3
+ # methods are cloning the receiver tree and perform their operation on the
4
+ # duplicated object. Some other are helpers adding reflexivity to trees.
5
+ # There are also a few in-place methods.
6
+ #
7
+ module Extended
8
+ # Non destructive version of {RawTreeBuilder#add_node!} (works on a copy of the tree).
9
+ #
10
+ # @overload add_node!(n)
11
+ # @param [node] n
12
+ # @overload add_node!(b)
13
+ # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
14
+ # @return [Tree] a modified copy of `self`
15
+ # @see RawTreeBuilder#add_node!
16
+ #
17
+ def add_node(n, l = nil)
18
+ _clone.add_node!(n, l)
19
+ end
20
+
21
+ # Non destructive version {RawTreeBuilder#add_branch!} (works on a copy of the tree).
22
+ #
23
+ # @overload add_branch!(i, j, l = nil)
24
+ # @param [node] i
25
+ # @param [node] j
26
+ # @param [Label] l
27
+ # @overload add_branch!(b)
28
+ # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
29
+ # @return [Tree] a modified copy of `self`
30
+ # @see RawTreeBuilder#add_branch!
31
+ #
32
+ def add_branch(i, j = nil, l = nil)
33
+ _clone.add_branch!(i, j, l)
34
+ end
35
+
36
+ # Non destructive version of {RawTreeBuilder#remove_node!} (works on a copy of the tree).
37
+ #
38
+ # @param [node] n
39
+ # @return [Tree] a modified copy of `self`
40
+ # @see RawTreeBuilder#remove_node!
41
+ #
42
+ def remove_node(n)
43
+ _clone.remove_node!(n)
44
+ end
45
+
46
+ # Non destructive version {RawTreeBuilder#remove_branch!} (works on a copy of the tree).
47
+ #
48
+ # You cannot remove non terminal branches as it would break the connectivity constraint
49
+ # of the tree.
50
+ #
51
+ # @param [node] i
52
+ # @param [node] j
53
+ # @return [Tree] a modified copy of `self`
54
+ # @see RawTreeBuilder#remove_branch!
55
+ #
56
+ def remove_branch(i, j = nil, *params)
57
+ _clone.remove_branch!(i, j)
58
+ end
59
+
60
+ # Adds all specified nodes to the node set.
61
+ #
62
+ # The nodes defines implicit branches, that is it is mandatory to provide
63
+ # an odd number of connected nodes which do not form cycles. You may specify
64
+ # Branch objects within the list, though.
65
+ #
66
+ # Valid usage:
67
+ #
68
+ # tree = Tree.new # an empty tree
69
+ # tree.add_nodes!(1,2, 2,3, 3,4) # tree.nodes => [1, 2, 3, 4]
70
+ # # branches are (1-2), (2-3), (3-4)
71
+ # tree.add_nodes!(1,2, 2,3, Branch.new(3,4), 10,1)
72
+ # tree.add_nodes! [1,2, 2,3, 3,4]
73
+ #
74
+ # Invalid usages:
75
+ #
76
+ # tree = Tree.new # an empty tree
77
+ # tree.add_nodes!(1, 2, 3) # even number of nodes
78
+ # tree.add_nodes!(1,2, 2,3, 3,1) # cycle
79
+ # tree.add_nodes!(1,2, 4,5) # not connected
80
+ #
81
+ # A helper exist to make it easy to add nodes in a suite.
82
+ # See {TreeBuilder#add_consecutive_nodes! add_consecutive_nodes!}.
83
+ #
84
+ # @param [#each] *a an Enumerable nodes set
85
+ # @return [Tree] `self`
86
+ #
87
+ def add_nodes!(*a)
88
+ a = a.flatten.expand_branches!
89
+ if a.size == 1 # This allows for using << to add the root as well,
90
+ if empty? # so that you may write:
91
+ add_node! a.only # Tree.new << :root << [:root, 1] << [1,2, 2,3, Branch.new(3,4)]...
92
+ else
93
+ raise RawTreeError, "Cannot add a node without a neighbour."
94
+ end
95
+ else
96
+ a.expand_branches!.each_nodes_pair { |pair| add_branch! pair[0], pair[1] }
97
+ end
98
+ self
99
+ end
100
+ alias << add_nodes!
101
+
102
+ # Same as {TreeBuilder#add_nodes! add_nodes!} but works on a copy of the receiver.
103
+ #
104
+ # @param [#each] *a an Enumerable nodes set
105
+ # @return [Tree] a modified copy of `self`
106
+ #
107
+ def add_nodes(*a)
108
+ _clone.add_nodes!(*a)
109
+ end
110
+
111
+ # Allows for adding consecutive nodes, each nodes being connected to their previous and
112
+ # next neighbours.
113
+ #
114
+ # You may pass {Branch branches} within the nodes list.
115
+ #
116
+ # Tree.new.add_consecutive_nodes!(1, 2, 3, :four, Branch.new(:foo, "bar")).branches
117
+ # # => (1=2, 2=3, 3=:four, :four=:foo, :foo="bar")
118
+ #
119
+ # @param [Array(nodes)] *a flat array of unique nodes
120
+ # @return [Tree] `self`
121
+ #
122
+ def add_consecutive_nodes!(*a)
123
+ # FIXME: really this may not be as efficient as it could be.
124
+ # We may get rid of nodes duplication (no expand_branches!)
125
+ # and add nodes by pair, shifting one node at a time from the list.
126
+ add_nodes!(a.expand_branches!.create_nodes_pairs_list)
127
+ self
128
+ end
129
+
130
+ # Same as {TreeBuilder#add_consecutive_nodes! add_consecutive_nodes!} but
131
+ # works on a copy of the receiver.
132
+ #
133
+ # @param [Array(nodes)] *a flat array of unique nodes
134
+ # @return [Tree] a modified copy of `self`
135
+ #
136
+ def add_consecutive_nodes(*a)
137
+ _clone.add_consecutive_nodes!(*a)
138
+ end
139
+
140
+ # Adds all branches mentionned in the specified Enumerable to the branch set.
141
+ #
142
+ # Elements of the Enumerable can be either two-element arrays or instances of
143
+ # {Branch}.
144
+ #
145
+ # @param [#each] *a an Enumerable branches set
146
+ # @return [Tree] `self`
147
+ #
148
+ def add_branches!(*a)
149
+ a.expand_branches!.each_nodes_pair { |pair| add_branch! pair[0], pair[1] }
150
+ self
151
+ end
152
+
153
+ # Same as {TreeBuilder#add_branches! add_branches!} but works on a copy of the receiver.
154
+ #
155
+ # @param [#each] *a an Enumerable branches set
156
+ # @return [Tree] a modified copy of `self`
157
+ #
158
+ def add_branches(*a)
159
+ _clone.add_branches!(*a)
160
+ end
161
+
162
+ # Removes all nodes mentionned in the specified Enumerable from the tree.
163
+ #
164
+ # The process relies on {RawTreeBuilder#remove_node! remove_node!}.
165
+ #
166
+ # @param [#each] *a an Enumerable nodes set
167
+ # @return [Tree] `self`
168
+ #
169
+ def remove_nodes!(*a)
170
+ a.flatten.each { |v| remove_node! v }
171
+ self
172
+ end
173
+ alias delete_nodes! remove_nodes!
174
+
175
+ # Same as {TreeBuilder#remove_nodes! remove_nodes!} but works on a copy of the receiver.
176
+ #
177
+ # @param [#each] *a a node Enumerable set
178
+ # @return [Tree] a modified copy of `self`
179
+ #
180
+ def remove_nodes(*a)
181
+ _clone.remove_nodes!(*a)
182
+ end
183
+ alias delete_nodes remove_nodes
184
+
185
+ # Removes all branches mentionned in the specified Enumerable from the tree.
186
+ #
187
+ # @param [#each] *a an Enumerable branches set
188
+ # @return [Tree] `self`
189
+ #
190
+ def remove_branches!(*a)
191
+ a.expand_branches!.each_nodes_pair { |pair| remove_branch! pair[0], pair[1] }
192
+ self
193
+ end
194
+ alias delete_branches! remove_branches!
195
+
196
+ # Same as {TreeBuilder#remove_branches! remove_branches!} but works on a copy of the receiver.
197
+ #
198
+ # @param [#each] *a an Enumerable branches set
199
+ # @return [Tree] a modified copy of `self`
200
+ # @see RawTreeBuilder#remove_branch!
201
+ #
202
+ def remove_branches(*a)
203
+ _clone.remove_branches!(*a)
204
+ end
205
+ alias delete_branches remove_branches
206
+
207
+ # Returns true if the specified node belongs to the tree.
208
+ #
209
+ # This is a default implementation that is of O(n) average complexity.
210
+ # If a subclass uses a hash to store nodes, then this can be
211
+ # made into an O(1) average complexity operation.
212
+ #
213
+ # @param [node] n
214
+ # @return [Boolean]
215
+ #
216
+ def node?(n)
217
+ nodes.include?(n)
218
+ end
219
+ alias has_node? node?
220
+
221
+ # Returns true if all specified nodes belong to the tree.
222
+ #
223
+ # @param [nodes]
224
+ # @return [Boolean]
225
+ #
226
+ def nodes?(*maybe_nodes)
227
+ maybe_nodes.all? { |node| nodes.include? node }
228
+ end
229
+ alias has_nodes? nodes?
230
+
231
+ # Returns true if any of the specified nodes belongs to the tree.
232
+ #
233
+ # @param [nodes]
234
+ # @return [Boolean]
235
+ #
236
+ def nodes_among?(*maybe_nodes)
237
+ maybe_nodes.any? { |node| nodes.include? node }
238
+ end
239
+ alias has_nodes_among? nodes_among?
240
+ alias has_node_among? nodes_among?
241
+ # FIXME: these helpers could be backported into Plexus.
242
+
243
+ # Returns true if i or (i,j) is a {Branch branch} of the tree.
244
+ #
245
+ # @overload branch?(a)
246
+ # @param [Branch] a
247
+ # @overload branch?(i, j)
248
+ # @param [node] i
249
+ # @param [node] j
250
+ # @return [Boolean]
251
+ #
252
+ def branch?(*args)
253
+ branches.include?(edge_convert(*args))
254
+ end
255
+ alias has_branch? branch?
256
+
257
+ # Returns true if the specified branches are a subset of or match exactly the
258
+ # branches set of the tree (no ordering criterion).
259
+ #
260
+ # Labels are not part of the matching constraint: only connected nodes
261
+ # matter in defining branch equality.
262
+ #
263
+ # @param [*Branch, *nodes] *maybe_branches a list of branches, either as Branch
264
+ # objects or as nodes pairs
265
+ # @return [Boolean]
266
+ #
267
+ def branches?(*maybe_branches)
268
+ list = maybe_branches.create_branches_list(_branch_type)
269
+ all = true
270
+
271
+ # Branch objects are really Edge objects within Plexus, therefore
272
+ # cannot rely on #eql? to compare those structures and must drop
273
+ # down to the attributes.
274
+ list.each do |e| # Jumoku::Branch structs
275
+ all = branches.any? do |b| # Plexus::Edge structs
276
+ (b[:source] == e[:source]) and (b[:target] == e[:target])
277
+ end
278
+ end
279
+ all
280
+ end
281
+ alias has_branches? branches?
282
+
283
+ # Returns true if actual branches are included in the specified set of branches
284
+ # (no ordering criterion).
285
+ #
286
+ # Labels are not part of the matching constraint: only connected nodes
287
+ # matter in defining equality.
288
+ #
289
+ # @param [*Branch, *nodes] *maybe_branches a list of branches, either as Branch
290
+ # objects or as nodes pairs
291
+ # @return [Boolean]
292
+ #
293
+ def branches_among?(*maybe_branches)
294
+ list = maybe_branches.create_branches_list(_branch_type)
295
+ all = true
296
+
297
+ # Branch objects are really Edge objects within Plexus, therefore
298
+ # cannot rely on #eql? to compare those structures and must drop
299
+ # down to the attributes.
300
+ branches.each do |e| # Plexus::Edge structs
301
+ all = list.any? do |b| # Jumoku::Branch structs
302
+ (b[:source] == e[:source]) and (b[:target] == e[:target])
303
+ end
304
+ end
305
+ all
306
+ end
307
+ alias has_branches_among? branches_among?
308
+
309
+ # Number of nodes.
310
+ #
311
+ # @return [Integer]
312
+ #
313
+ def num_nodes
314
+ nodes.size
315
+ end
316
+ alias number_of_nodes num_nodes
317
+
318
+ # Number of branches.
319
+ #
320
+ # @return [Integer]
321
+ #
322
+ def num_branches
323
+ branches.size
324
+ end
325
+ alias number_of_branches num_branches
326
+ end
327
+ end
@@ -0,0 +1,33 @@
1
+ module Jumoku
2
+ # A {RawDirectedTree} relax the undirected constraint of trees as defined in
3
+ # Graph Theory. They remain connected and acyclic.
4
+ #
5
+ # It thus uses Plexus::DirectedGraphBuilder as its backend.
6
+ #
7
+ # It offers limited functionalities, therefore the main tree structure you'll likely to
8
+ # use is its extended version, {Arborescence}.
9
+ module RawDirectedTreeBuilder
10
+ include Plexus::DirectedGraphBuilder
11
+ include Shared
12
+
13
+ # This method is called by the specialized implementations upon tree creation.
14
+ #
15
+ # Initialization parameters can include:
16
+ #
17
+ # * an array of branches to add
18
+ # * one or several trees to copy (will be merged if multiple)
19
+ #
20
+ # @param *params [Hash] the initialization parameters
21
+ # @return enhanced Plexus::DirectedGraph
22
+ #
23
+ def initialize(*params)
24
+ super(*params) # Delegates to Plexus.
25
+ end
26
+
27
+ private
28
+
29
+ def _branch_type
30
+ DirectedBranch
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ module Jumoku
2
+ # A {RawUndirectedTree} sticks to the standard definition of trees in Graph Theory:
3
+ # * undirected,
4
+ # * connected,
5
+ # * acyclic.
6
+ # It thus uses Plexus::UndirectedGraphBuilder as its backend, which ensure the first
7
+ # constraint.
8
+ #
9
+ # {RawUndirectedTreeBuilder} ensures the two remaining constraints are satisfied.
10
+ # It offers limited functionalities, therefore the main tree structure you'll likely to
11
+ # use is its extended version, {TreeBuilder}.
12
+ #
13
+ module RawUndirectedTreeBuilder
14
+ include Plexus::UndirectedGraphBuilder
15
+ include Shared
16
+
17
+ # This method is called by the specialized implementations upon tree creation.
18
+ #
19
+ # Initialization parameters can include:
20
+ #
21
+ # * an array of branches to add
22
+ # * one or several trees to copy (will be merged if multiple)
23
+ #
24
+ # @param *params [Hash] the initialization parameters
25
+ # @return enhanced Plexus::UndirectedGraph
26
+ #
27
+ def initialize(*params)
28
+ super(*params) # Delegates to Plexus.
29
+ end
30
+
31
+ private
32
+
33
+ def _branch_type
34
+ UndirectedBranch
35
+ end
36
+ end
37
+ end