jumoku 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Jean-Denis Vauguet <jd@vauguet.fr>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,112 @@
1
+ ![Texte alternatif](http://img547.imageshack.us/img547/2932/logoJumoku.png "Jumoku logo")
2
+
3
+ **Build and manipulate tree structures in Ruby.**
4
+
5
+ ## Synopsis
6
+
7
+ Jumoku provides you with tree structures and related tools to perform manipulation and computation the easy way. Trees are a subset of graphs, which are used in a whole slew of computer science and mathematical problems: network modelization, datasets storage, scientific computation, load balancing, games and AI designs, … Trees are frequently used to mimic hierarchal structures such as filesystems, or modelize decisionnal patterns, for instance.
8
+
9
+ Jumoku is built upon [Graphy](http://github.com/bruce/graphy "Graphy on Github"), a ruby-powered Graph Theory library, and aims at being a fully-fledged, pithy solution for tree-like structures managment. See below for additionnal information about graphs, trees, arborescences, why they're different and how to make good use of them.
10
+
11
+ ## A few words about *trees*
12
+
13
+ A Tree is a graph subject to three basic constraints: nodes are all connected, they must not form any loop, and the branches binding nodes have no preferential direction. A tree is not compelled to have a root node and leaves as you may think *prima facie*. Trees with such features are called arborescences and Jumoku has support for them, too.
14
+
15
+ Jumoku (*currently under early development stage*) provides you with the following structures:
16
+
17
+ * RawTree: a tree-graph with only basic features
18
+ * **Tree**: a tree-graph with extended features built upon RawTree. That's what you'd want to use as a basic tree structure
19
+ * AVLTree (*not yet*)
20
+ * RedBlackTree (*not yet*)
21
+ * **Arborescence** (*not yet*): the structure everybody thinks of when asked about "trees", with a root, internal nodes and leaves
22
+
23
+ You can also extend those structures with hybrid behaviors (not Graph Theory compliant but may be useful):
24
+
25
+ * Directed (*not yet*): relax the *undirected* constraint
26
+ * Loopy (*not yet*): relax the *acyclic* constraint
27
+ * Atomic (*not yet*): relax the *connected* constraint
28
+
29
+ ## Basic usage
30
+
31
+ To create an instance of a tree, you may use either inheritance or mixins.
32
+
33
+ ### Inheritance or direct initialization
34
+
35
+ ``` ruby
36
+ class MyTree < Jumoku::Tree; end
37
+ tree = MyTree.new
38
+
39
+ # or even simpler:
40
+ tree = Jumoku::Tree.new
41
+ ```
42
+
43
+ ### Mixing-in a "builder" module
44
+
45
+ ``` ruby
46
+ class MyTree
47
+ include Jumoku::RawTreeBuilder
48
+ end
49
+ tree = MyTree.new
50
+ ```
51
+
52
+ Actually the RawTree class is nothing but the mixin code snippet above.
53
+
54
+ ### What you get
55
+
56
+ `tree` is now a Tree object, shipping with some default options:
57
+
58
+ * it is a valid tree *per se* (an undirected, acyclic, connected graph)
59
+ * Jumoku API's methods will ensure it remains so
60
+ * it has no constraint on its branching number (the number of branches per node)
61
+
62
+ ## Install, first steps and bootstraping
63
+
64
+ In order to play with Jumoku, you must:
65
+
66
+ ``` bash
67
+ gem install jumoku
68
+ ```
69
+
70
+ Now you can play in IRB:
71
+
72
+ ``` bash
73
+ $ irb
74
+ ruby-1.9.1-p378 > require 'Jumoku'
75
+ => true
76
+ ruby-1.9.1-p378 > include Jumoku # so you won't have to prefix everything with "Jumoku::"
77
+ => Object
78
+ ruby-1.9.1-p378 > t = Tree.new
79
+ => #<Jumoku::Tree:0x000000020d5ac8 @vertex_dict={}, @vertex_labels={}, @edge_labels={}, @allow_loops=false, @parallel_edges=false, @edgelist_class=Set>
80
+ ruby-1.9.1-p378 > t.methods
81
+ => # lot of stuff hopefully :)
82
+ ```
83
+
84
+ A good way to get you started is by [reading the doc online](http://rdoc.info/projects/chikamichi/Jumoku "Jumoku on rdoc.info"). You can locally generate the doc with any of the following commands (you'll need to have the `yard` gem installed):
85
+
86
+ ``` bash
87
+ rake yard
88
+ yardoc
89
+ ```
90
+
91
+ Be sure to take a look at the tests in `spec/` as well, as they document the public API extensively.
92
+
93
+ ## What about graphs?
94
+
95
+ Trees are just simple graphs, with specific constraints on edges (branches) and vertices (nodes). In graph theory, a tree is defined as a graph which is undirected, acyclic and connected. A forest is a disjoint union of trees. Let's review those constraints:
96
+
97
+ * undirected: the branches of a tree have no direction;
98
+ * acyclic: a cycle is defined as a path such that the starting node and the ending node are the same. Such paths are forbidden in a tree;
99
+ * connected: a tree is such that pair of distinct nodes can be connected through some path (not a cycle).
100
+
101
+ This is restrictive, but not *that* restrictive. Instinctively, a tree structure would rather be described as an arborescence, with a root and some leaves. That's what you would use to use to modelize nested directories, for instance. Such a structure has additional constraints, and it makes sense to derive it from the more generalist tree*-as-in-graph-theory* structure, under the name "arborescence".
102
+
103
+ Therefore, it is easy to implement those structures using graphs. Fortunately, several ruby graph libraries exist (and even a ruby binding for the igraph C backend, but that'd be a burden to use). Jumoku makes use of the most up-to-date project among those available, [Graphy](http://github.com/bruce/graphy "Graphy on Github").
104
+
105
+ ## License
106
+
107
+ See the LICENSE file.
108
+
109
+ ## Contributing
110
+
111
+ Fork, hack, request!
112
+
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+ require 'rubygems'
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rake'
10
+ require 'rake/rdoctask'
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+
15
+ RSpec::Core::RakeTask.new(:spec)
16
+
17
+ task :default => :spec
18
+
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'Logg'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README.rdoc')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
@@ -0,0 +1,21 @@
1
+ $:.unshift File.dirname(__FILE__) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'pathname'
5
+ require 'pp'
6
+ require 'active_support'
7
+ require 'facets'
8
+ require 'graphy'
9
+
10
+ module Jumoku
11
+ # jumoku internals: graph builders and additionnal behaviors
12
+ autoload :TreeAPI, 'jumoku/tree_api'
13
+ autoload :RawTreeBuilder, 'jumoku/builders/raw_tree'
14
+ autoload :TreeBuilder, 'jumoku/builders/tree'
15
+ autoload :RawTree, 'jumoku/classes/tree_classes'
16
+ autoload :Tree, 'jumoku/classes/tree_classes'
17
+ autoload :Branch, 'jumoku/support/branch'
18
+
19
+ require 'jumoku/support/support'
20
+ require 'jumoku/ext/ext'
21
+ end
@@ -0,0 +1,243 @@
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}.
5
+ #
6
+ # A {RawTree} sticks to the standard definition of trees in Graph Theory: undirected,
7
+ # connected, acyclic graphs. Using Graphy::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 Graphy::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
41
+ end
42
+ super(*params) # Delegates to Graphy.
43
+ end
44
+
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 Graphy's method ?!
52
+
53
+ # Adds the node to the tree.
54
+ #
55
+ # For convenience, you may pass a branch as the parameter,
56
+ # which one node already exists in the tree and the other is
57
+ # to be added.
58
+ #
59
+ # @overload add_node!(n)
60
+ # @param [node] n
61
+ # @overload add_node!(b)
62
+ # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
63
+ # @return [RawTree] self
64
+ def add_node! u, v = nil
65
+ if nodes.empty?
66
+ add_vertex! u
67
+ elsif u.is_a? Jumoku::Branch
68
+ add_branch! u
69
+ elsif not v.nil?
70
+ add_branch! u, v
71
+ else
72
+ # Ensure the connected constraint.
73
+ raise RawTreeError, "In order to add a node to the tree, you must specify another node to attach to."
74
+ end
75
+ end
76
+
77
+ # Adds a new branch to the tree.
78
+ #
79
+ # As a tree is an undirected structure, the order of the parameters is of
80
+ # no importance, that is: `add_branch!(u,v) == add_branch!(v,u)`.
81
+ #
82
+ # @overload add_branch!(i, j, l = nil)
83
+ # @param [node] i
84
+ # @param [node] j
85
+ # @param [Label] l
86
+ # @overload add_branch!(b)
87
+ # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
88
+ # @return [RawTree] self
89
+ def add_branch! u, v = nil, l = nil
90
+ if has_node? u and has_node? v
91
+ unless has_branch? u, v
92
+ # Ensure the acyclic constraint.
93
+ raise RawTreeError, "Can't form a cycle within a tree."
94
+ end
95
+ end
96
+
97
+ # TODO: DRY this up.
98
+ if u.is_a? Jumoku::Branch
99
+ v = u.target
100
+ u = u.source
101
+ end
102
+
103
+ if has_node? u or has_node? v or nodes.empty?
104
+ add_edge! u, v, l
105
+ else
106
+ # Ensure the connected constraint.
107
+ raise RawTreeError, "Can't add a dead branch to a tree."
108
+ end
109
+ end
110
+
111
+ # Removes a node from the tree.
112
+ #
113
+ # You cannot remove non terminal nodes as it would break the
114
+ # connectivity constraint of the tree.
115
+ #
116
+ # I may add an option which would allow to force removal
117
+ # of internal nodes and return two new trees from this
118
+ # destructive operation.
119
+ #
120
+ # @param [node] u
121
+ # @return [RawTree] self
122
+ def remove_node!(u, force = false)
123
+ if terminal? u or force
124
+ remove_vertex! u
125
+ else
126
+ # Ensure the connected constraint.
127
+ raise RawTreeError, "Can't remove a non terminal node in a tree"
128
+ end
129
+ end
130
+
131
+ # Removes a branch from the tree.
132
+ #
133
+ # You cannot remove non terminal branches as it would break
134
+ # the connectivity constraint of the tree.
135
+ #
136
+ # I may add an option which would allow to force removal
137
+ # of internal nodes and return two new trees from this
138
+ # destructive operation.
139
+ #
140
+ # @overload remove_branch!(i, j)
141
+ # @param [node] i
142
+ # @param [node] j
143
+ # @overload remove_branch!(b)
144
+ # @param [Branch] b
145
+ # @return [RawTree] self
146
+ def remove_branch! u, v = nil
147
+ if u.is_a? Jumoku::Branch
148
+ v = u.target
149
+ u = u.source
150
+ end
151
+
152
+ if has_node? u and has_node? v
153
+ if terminal? u and terminal? v
154
+ remove_edge! u, v
155
+ # empty the tree if u and v are the last two nodes, for they're
156
+ # not connected anymore
157
+ if [u, v].all? { |node| nodes.include? node } and nodes.size == 2
158
+ remove_node! u, true
159
+ remove_node! v, true
160
+ end
161
+ elsif terminal? u and not terminal? v
162
+ remove_node! u
163
+ elsif terminal? v and not terminal? u
164
+ remove_node! v
165
+ else
166
+ raise RawTreeError, "Can't remove a non terminal branch in a tree."
167
+ end
168
+ else
169
+ raise RawTreeError, "Can't remove a branch which does not exist."
170
+ end
171
+ end
172
+ # TODO: add an option to force nodes deletion upon branch deletion
173
+ # (:and_nodes => true) ; called by a wrapping method
174
+ # remove_branch_and_nodes, alias break_branch
175
+ # (and their ! roomates).
176
+
177
+ # The nodes of the tree in a 1D array.
178
+ #
179
+ # @return [Array(node)]
180
+ def nodes
181
+ vertices
182
+ end
183
+
184
+ # The terminal nodes of the tree in a 1D array.
185
+ #
186
+ # @return [Array(node)] only terminal nodes (empty array if no terminal nodes,
187
+ # but should never happen in a tree).
188
+ 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).
194
+ end
195
+ end
196
+ alias boundaries terminal_nodes
197
+
198
+ # The branches of the tree in a 1D array.
199
+ #
200
+ # @return [Array(Branch)]
201
+ def branches
202
+ edges
203
+ end
204
+
205
+ # Aliasing.
206
+ alias has_node? has_vertex?
207
+ alias has_branch? has_edge?
208
+
209
+ # Tree helpers.
210
+
211
+ # Checks whether the tree is *really* a valid tree, that is if the
212
+ # following conditions are fulfilled:
213
+ #
214
+ # * undirected
215
+ # * acyclic
216
+ # * connected
217
+ #
218
+ # @return [Boolean]
219
+ def valid?
220
+ acyclic? and connected? and not directed?
221
+ end
222
+
223
+ # Is the node a terminal node?
224
+ #
225
+ # @return [Boolean]
226
+ def terminal? u
227
+ if has_node? u
228
+ # root is always terminal, otherwise check whether degree is unitary
229
+ nodes == [u] ? true : (degree(u) == 1)
230
+ else
231
+ raise RawTreeNodeError, "Not a node of this tree."
232
+ end
233
+ end
234
+ alias has_terminal_node? terminal?
235
+
236
+ # Is the tree empty?
237
+ #
238
+ # @return [Boolean]
239
+ def empty?
240
+ nodes.empty?
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,336 @@
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.
6
+ #
7
+ # tree = Tree.new
8
+ #
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
+ 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 Graphy.
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 Graphy, 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| # Graphy::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 Graphy, therefore
309
+ # cannot rely on #eql? to compare those structures and must drop
310
+ # down to the attributes.
311
+ branches.each do |e| # Graphy::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
335
+ end
336
+ end
@@ -0,0 +1,7 @@
1
+ module Jumoku
2
+ # A generic {RawTreeBuilder RawTree} class you can inherit from.
3
+ class RawTree; include RawTreeBuilder; end
4
+
5
+ # The main tree class to use. Fully-fledged tree structure with user-friendly public API.
6
+ class Tree; include TreeBuilder; end
7
+ end
@@ -0,0 +1,50 @@
1
+ class Array
2
+ # Takes a flat array of nodes pairs and yield
3
+ # them to the block.
4
+ def each_nodes_pair
5
+ raise if self.size % 2 != 0
6
+ each_by(2) { |pair| yield pair }
7
+ end
8
+
9
+ # Fetches the positions of Branch items in an array,
10
+ # and expands them in place as nodes pairs.
11
+ #
12
+ # expand_branches!(1,2, Branch.new(2, 3), 3,4, 4,5)
13
+ # # => 1,2, 2,3, 3,4, 4,5
14
+ #
15
+ # @param [*nodes, *Branch] *a a flat list of nodes pairs and branches
16
+ # @return [*nodes] a flat list of nodes pairs
17
+ def expand_branches!
18
+ branches_positions = self.dup.map_send(:"is_a?", Jumoku::Branch).map_with_index { |e,i| i if e == true }.compact
19
+ # obviously positions will gain an offset of 1 for each expanded branch,
20
+ # so let's correct this right away
21
+ branches_positions = branches_positions.map_with_index { |e,i| e += i }
22
+ branches_positions.each do |pos|
23
+ branch = self.delete_at pos
24
+ self.insert pos, branch.source
25
+ self.insert pos+1, branch.target
26
+ end
27
+ return self
28
+ end
29
+
30
+ def create_pairs
31
+ self.map_with_index { |e,i| ((pairs ||= []) << e).concat(self.values_at(i+1)) }[0..-2]
32
+ end
33
+
34
+ # Creates nodes pairs from a flat array specifying unique nodes.
35
+ #
36
+ # b = [1, 2, 3, "foo", :bar, Array.new]
37
+ # b.create_nodes_pairs
38
+ # => [1, 2, 2, 3, 3, "foo", "foo", :bar, :bar, []]
39
+ #
40
+ # @return [Array(nodes)]
41
+ def create_nodes_pairs_list
42
+ self.create_pairs.flatten(1)
43
+ end
44
+
45
+ def create_branches_list
46
+ branches = []
47
+ self.expand_branches!.each_by(2) { |pair| branches << Jumoku::Branch.new(pair[0], pair[1]) }
48
+ branches
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ module Jumoku
2
+ module RawTree
3
+
4
+ # This module describes a raw tree node.
5
+ #
6
+ # You can include it in a class of your own to make it act
7
+ # as a raw tree node, while customizing behaviors.
8
+ module Node
9
+
10
+ # Creates a new node.
11
+ #
12
+ # @param [#to_s, #to_sym] name a valid name, considering the :names_as options
13
+ # passed to the raw tree you want to create a node for.
14
+ # @param [Object] data the data field type to use. Defaults to an empty OpenObject
15
+ # @param [Array(#to_s)] children an array of children nodes ids
16
+ #
17
+ # @return [OpenObject] the node as an OpenObject instance
18
+ def create name, data = OpenObject.new, children = []
19
+ @name = name
20
+ @data = data
21
+ @children = children
22
+
23
+ OpenObject.new(:name => @name, :data => @data, :children => @children)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ module Jumoku
2
+ class Branch < Graphy::Edge
3
+ end
4
+ end
@@ -0,0 +1,15 @@
1
+ module Jumoku
2
+ # Base error class for the library.
3
+ class JumokuError < StandardError; end
4
+
5
+ # Raised in several circumstances when the basic constraints related
6
+ # to a tree are not fulfilled (undirected, acyclic, connected).
7
+
8
+ class RawTreeError < JumokuError; end
9
+ # Error related to nodes.
10
+
11
+ class RawTreeNodeError < RawTreeError; end
12
+ # Raised if one attempts to add an already existing branch to a tree.
13
+
14
+ class BranchAlreadyExistError < JumokuError; end
15
+ end
@@ -0,0 +1,27 @@
1
+ module Jumoku
2
+ # This module defines the minimum set of functions required to make a tree that can
3
+ # use the algorithms defined by this library.
4
+ #
5
+ # Each implementation module must implement the following routines:
6
+ #
7
+ # * add_node!(n, l = nil) — adds a node to the tree and return the tree; l is an optional label (see Graphy library).
8
+ # * add_branch!(i, j = nil, l = nil) — adds a branch to the tree and return the tree. i can be a {Branch}, or (i,j) a node pair; l is an optional label.
9
+ # * remove_node!(n) — removes a node from the tree and return the tree.
10
+ # * remove_branch!(i, j = nil) — removes an edge from the graph and return the tree. i can be a {Branch}, or (i,j) a node pair.
11
+ # * nodes — returns an array of all nodes.
12
+ # * terminal_nodes — returns an array of all terminal nodes.
13
+ # * branches — returns an array of all branches.
14
+ module TreeAPI
15
+
16
+ # @raise if the API is not completely implemented
17
+ def self.included(klass)
18
+ @api_methods ||= [:add_node!, :add_branch!, :remove_node!, :remove_branch!, :nodes, :terminal_nodes, :branches]
19
+ ruby_18 { @api_methods.each { |m| m.to_s } }
20
+
21
+ @api_methods.each do |meth|
22
+ raise "Must implement #{meth}" unless klass.instance_methods.include?(meth)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ module Jumoku
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ PATCH = 1
5
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
6
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jumoku
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.1
6
+ platform: ruby
7
+ authors:
8
+ - Jean-Denis Vauguet <jd@vauguet.fr>
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-07-06 00:00:00 +02:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: facets
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: yard
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ description: Jumoku provides you with tree behaviors to mixin and tree classes to inherit from. Raw tree, common binary trees, custom trees...
61
+ email: jd@vauguet.fr
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files: []
67
+
68
+ files:
69
+ - lib/jumoku.rb
70
+ - lib/jumoku/tree_api.rb
71
+ - lib/jumoku/ext/ext.rb
72
+ - lib/jumoku/builders/raw_tree.rb
73
+ - lib/jumoku/builders/tree.rb
74
+ - lib/jumoku/raw_tree_node.rb
75
+ - lib/jumoku/classes/tree_classes.rb
76
+ - lib/jumoku/support/support.rb
77
+ - lib/jumoku/support/branch.rb
78
+ - lib/jumoku/version.rb
79
+ - LICENSE
80
+ - Rakefile
81
+ - README.md
82
+ has_rdoc: true
83
+ homepage: http://github.com/chikamichi/jumoku
84
+ licenses: []
85
+
86
+ post_install_message:
87
+ rdoc_options: []
88
+
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: "0"
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project:
106
+ rubygems_version: 1.6.1
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: A fully fledged tree library for Ruby.
110
+ test_files: []
111
+