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 +20 -0
- data/README.md +112 -0
- data/Rakefile +25 -0
- data/lib/jumoku.rb +21 -0
- data/lib/jumoku/builders/raw_tree.rb +243 -0
- data/lib/jumoku/builders/tree.rb +336 -0
- data/lib/jumoku/classes/tree_classes.rb +7 -0
- data/lib/jumoku/ext/ext.rb +50 -0
- data/lib/jumoku/raw_tree_node.rb +27 -0
- data/lib/jumoku/support/branch.rb +4 -0
- data/lib/jumoku/support/support.rb +15 -0
- data/lib/jumoku/tree_api.rb +27 -0
- data/lib/jumoku/version.rb +6 -0
- metadata +111 -0
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.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+

|
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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/lib/jumoku.rb
ADDED
@@ -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,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,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
|
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
|
+
|