jumoku 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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