jumoku 0.1.1

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