jumoku 0.2.2 → 0.2.3

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/Rakefile CHANGED
@@ -7,19 +7,7 @@ rescue LoadError
7
7
  end
8
8
 
9
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')
10
+ desc "Open an irb session preloaded with this library"
11
+ task :console do
12
+ sh "irb -rubygems -I lib -r jumoku.rb"
25
13
  end
@@ -5,6 +5,7 @@ require 'pathname'
5
5
  require 'pp'
6
6
  require 'active_support'
7
7
  require 'facets'
8
+ require 'hashery'
8
9
  require 'plexus'
9
10
 
10
11
  # Jumoku provides you with several modules and classes to build and manipulate
@@ -43,6 +44,10 @@ module Jumoku
43
44
  autoload :Tree, 'jumoku/classes/tree_classes'
44
45
  autoload :Arborescence, 'jumoku/classes/tree_classes'
45
46
 
47
+ # strategies
48
+ autoload :EdgeLabeling, 'jumoku/strategies/edge_labeling'
49
+ EdgeLabeling.autoload :Simple, 'jumoku/strategies/edge_labeling/simple'
50
+
46
51
  # support
47
52
  require 'jumoku/support/ruby_compatibility'
48
53
  require 'jumoku/support/support'
@@ -7,5 +7,19 @@ module Jumoku
7
7
  module ArborescenceBuilder
8
8
  include RawDirectedTreeBuilder
9
9
  include Extended
10
+
11
+ def leaves
12
+ terminal_nodes.delete_if do |node|
13
+ out_degree(node) > 0
14
+ end
15
+ end
16
+
17
+ def leaf?(node)
18
+ terminal?(node) && out_degree(node) == 0
19
+ end
20
+
21
+ def leaves?(*nodes)
22
+ nodes.to_a.flatten.all? { |node| leaf?(node) }
23
+ end
10
24
  end
11
25
  end
@@ -21,8 +21,13 @@ module Jumoku
21
21
  # @return enhanced Plexus::DirectedGraph
22
22
  #
23
23
  def initialize(*params)
24
+ args = (params.pop if params.last.is_a? Hash) || {}
25
+ strategies = _extract_strategies(args)
26
+
24
27
  super(*params) # Delegates to Plexus.
28
+
25
29
  class << self; self; end.module_eval do
30
+ strategies.each { |strategy| include strategy }
26
31
  alias has_branch? has_arc?
27
32
  end
28
33
  end
@@ -25,8 +25,13 @@ module Jumoku
25
25
  # @return enhanced Plexus::UndirectedGraph
26
26
  #
27
27
  def initialize(*params)
28
+ args = (params.pop if params.last.is_a? Hash) || {}
29
+ strategies = _extract_strategies(args)
30
+
28
31
  super(*params) # Delegates to Plexus.
32
+
29
33
  class << self; self; end.module_eval do
34
+ strategies.each { |strategy| include strategy }
30
35
  alias has_branch? has_edge?
31
36
  end
32
37
  end
@@ -21,13 +21,13 @@ module Jumoku
21
21
  # @overload add_node!(b)
22
22
  # @param [Branch] b Branch[node i, node j, label l = nil]; if i (j) already exists, then j (i) must not exist
23
23
  # @return [RawTree] self
24
- def add_node! u, v = nil
24
+ def add_node! u, v = nil, l = nil
25
25
  if nodes.empty?
26
- add_vertex! u
26
+ add_vertex! u, l
27
27
  elsif u.is_a? _branch_type
28
- add_branch! u
28
+ add_branch! u, nil, l
29
29
  elsif not v.nil?
30
- add_branch! u, v
30
+ add_branch! u, v, l
31
31
  else
32
32
  # Ensure the connected constraint.
33
33
  raise RawTreeError, "In order to add a node to the tree, you must specify another node to attach to."
@@ -153,7 +153,6 @@ module Jumoku
153
153
  end
154
154
  end
155
155
  alias boundaries terminal_nodes
156
- alias leaves terminal_nodes
157
156
 
158
157
  # The branches of the tree in a 1D array.
159
158
  #
@@ -164,10 +163,9 @@ module Jumoku
164
163
 
165
164
  # Tree helpers.
166
165
 
167
- # Checks whether the tree is *really* a valid tree, that is if the
168
- # following conditions are fulfilled:
166
+ # Checks whether the tree is a valid tree (directed or undirected), that is
167
+ # if the following conditions are fulfilled:
169
168
  #
170
- # * undirected
171
169
  # * acyclic
172
170
  # * connected
173
171
  #
@@ -180,12 +178,9 @@ module Jumoku
180
178
  #
181
179
  # @return [Boolean]
182
180
  def terminal? u
183
- if has_node? u
184
- # root is always terminal, otherwise check whether degree is unitary
185
- nodes == [u] ? true : (degree(u) == 1)
186
- else
187
- raise UndefinedNode, "Not a node of this tree."
188
- end
181
+ raise UndefinedNode, "Not a node of this tree." unless has_node? u
182
+ return true if (1..2).include? nodes.size
183
+ degree(u) == 1
189
184
  end
190
185
  alias has_terminal_node? terminal?
191
186
  alias leaf? terminal?
@@ -211,5 +206,16 @@ module Jumoku
211
206
  def _clone
212
207
  self.class.new(self)
213
208
  end
209
+
210
+ def _extract_strategies(options)
211
+ options.inject([]) do |strategies, (k,v)|
212
+ begin
213
+ strategies << Jumoku.const_get(k.to_s.constantize).const_get(v.to_s.constantize)
214
+ strategies
215
+ rescue NameError # silently ignored
216
+ strategies
217
+ end
218
+ end
219
+ end
214
220
  end
215
221
  end
@@ -1,3 +1,4 @@
1
+ # FIXME: nest this into Jumoku's namespace and inherit from stdlib classes
1
2
  class Array
2
3
  # Takes a flat array of nodes pairs and yield
3
4
  # them to the block.
@@ -54,3 +55,9 @@ class Array
54
55
  branches
55
56
  end
56
57
  end
58
+
59
+ class String
60
+ def constantize
61
+ to_s.gsub(/(_|^)(\w)/) { $2.upcase }
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ module Jumoku
2
+ module EdgeLabeling
3
+ module Backend
4
+ # Sort edges by the provided block's logic. The block takes edge as
5
+ # parameter, and this method delegates to Enumerable#sort_by for
6
+ # sorting edges. Return unsorted edges list if no block is provided.
7
+ #
8
+ # @return [Array]
9
+ #
10
+ def sort_edges(&block)
11
+ return edges.sort_by { |edge| block.call(edge) } if block_given?
12
+ edges
13
+ end
14
+
15
+ private
16
+
17
+ # Callback triggered when labeling a new edge. Does nothing by default.
18
+ # Labeling strategies may implement their own logic and call super to
19
+ # chain callbacks, or impose their behavior (caution!).
20
+ #
21
+ def _edge_labeling_inc
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ module Jumoku
2
+ module EdgeLabeling
3
+ # A simple edge labeling strategy: as new nodes are added, new edges are
4
+ # assigned increasing integers. Removed edges do not trigger reindexing,
5
+ # so for a tree with n nodes, the labeling set is ||n|| but indexes are
6
+ # not necessarily < n (indexing starts from 0).
7
+ #
8
+ # This simple strategy allows for using simple search algorithms as well
9
+ # for simple ordering schemes.
10
+ #
11
+ module Simple
12
+ include EdgeLabeling::Backend
13
+
14
+ attr_accessor :next_simple_edge_label_number
15
+
16
+ # Override to handle an iterator for simple edge labeling. When a branch
17
+ # is added, it is labeled with an OpenHash with one :_weight key which
18
+ # value is an increasing integer. One may pass any custom label to the
19
+ # method, which will be available under the :label key of the hash.
20
+ #
21
+ def add_branch!(u, v = nil, l = nil)
22
+ self.next_simple_edge_label_number ||= 0
23
+ label = OpenHash.new(:_weight => self.next_simple_edge_label_number)
24
+ label.label = l unless l.nil?
25
+ super(u, v, label)
26
+ _edge_labeling_inc
27
+ end
28
+
29
+ # Sort edges by increasing weight number. If a block is provided, rely
30
+ # on the backend implementation (#sort_by).
31
+ #
32
+ # @return [Array]
33
+ #
34
+ def sort_edges(&block)
35
+ return super(&block) if block_given?
36
+ edges.sort { |a,b| a.label._weight <=> b.label._weight }
37
+ end
38
+
39
+ # Only for directed trees.
40
+ #
41
+ # Return the sorted list of arcs branched out from the specified node.
42
+ #
43
+ # @param [Node] node
44
+ # @return [Array<Plexus::Arc>]
45
+ #
46
+ def sorted_arcs_from(node)
47
+ return nil unless is_a? RawDirectedTreeBuilder
48
+ adjacent(node).map do |child_node|
49
+ edge_class[node, child_node, edge_label(node, child_node)]
50
+ end.sort_by { |e| e.label._weight }
51
+ end
52
+ alias sorted_edges_from sorted_arcs_from # for lazy people
53
+
54
+ # Only for directed trees.
55
+ #
56
+ # Return the sorted list of nodes branched out from the specified node.
57
+ #
58
+ # @param [Node] node
59
+ # @return [Array<Node>]
60
+ #
61
+ def sorted_children_of(node)
62
+ return nil unless is_a? RawDirectedTreeBuilder
63
+ sorted_edges_from(node).map { |edge| edge.target }
64
+ end
65
+
66
+ private
67
+
68
+ def _edge_labeling_inc
69
+ self.next_simple_edge_label_number ||= 0
70
+ self.next_simple_edge_label_number += 1
71
+ super
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,6 +1,6 @@
1
1
  module Jumoku
2
2
  MAJOR = 0
3
3
  MINOR = 2
4
- PATCH = 2
4
+ PATCH = 3
5
5
  VERSION = [MAJOR, MINOR, PATCH].join('.')
6
6
  end
@@ -7,8 +7,42 @@ describe Arborescence do
7
7
  let(:branch_type) { subject.send :_branch_type }
8
8
 
9
9
  it_should_behave_like "a legacy tree"
10
+
10
11
  it "should be a directed graph" do
11
12
  tree.class.ancestors.should include RawDirectedTreeBuilder
12
13
  end
14
+
13
15
  it_should_behave_like "a tree with extended features"
16
+
17
+ context "leaf nodes inspection" do
18
+ before :each do
19
+ tree.add_branches! 1,2, 2,3, 1,4, 1,5
20
+ end
21
+
22
+ describe "#leaves" do
23
+ it "should list the leaf nodes" do
24
+ tree.leaves.sort.should == [3,4,5]
25
+ end
26
+ end
27
+
28
+ describe "#leaf?" do
29
+ it "should check whether a node is a leaf" do
30
+ tree.leaf?(1).should be_false
31
+ tree.leaf?(2).should be_false
32
+ tree.leaf?(3).should be_true
33
+ tree.leaf?(4).should be_true
34
+ tree.leaf?(5).should be_true
35
+ end
36
+ end
37
+
38
+ describe "#leaves?" do
39
+ it "should check whether each node of a list is a leaf" do
40
+ tree.leaves?(1).should be_false
41
+ tree.leaves?(1,2).should be_false
42
+ tree.leaves?(1,2,3).should be_false
43
+ tree.leaves?(3,4).should be_true
44
+ tree.leaves?(3,4,5).should be_true
45
+ end
46
+ end
47
+ end
14
48
  end
@@ -2,9 +2,6 @@ require File.expand_path("../../lib/jumoku.rb", __FILE__)
2
2
  require File.expand_path("../behaviors/core_tree.rb", __FILE__)
3
3
  require File.expand_path("../behaviors/extended.rb", __FILE__)
4
4
 
5
- require 'jumoku'
6
- include Jumoku
7
-
8
5
  RSpec.configure do |config|
9
6
  # Remove this line if you don't want RSpec's should and should_not
10
7
  # methods or matchers
@@ -13,4 +10,7 @@ RSpec.configure do |config|
13
10
 
14
11
  # == Mock Framework
15
12
  config.mock_with :rspec
13
+
14
+ require 'jumoku'
15
+ include Jumoku
16
16
  end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe EdgeLabeling::Simple do
4
+ let(:tree) { Tree.new(:edge_labeling => :simple) }
5
+ let(:arbo) { Arborescence.new(:edge_labeling => :simple) }
6
+
7
+ describe 'a tree with simple edge labeling' do
8
+ it "should label its edges with increasing integers when new nodes are added or removed" do
9
+ tree.add_node! 1
10
+ tree.add_node! 1, 2 # adding edge 0
11
+ edge = tree.edges.first
12
+ edge.label.should be_an OpenHash
13
+ edge.label._weight.should == 0
14
+
15
+ tree.add_branch! 2, 3 # adding edge 1
16
+ tree.edges.all? { |e| e.label.is_a? OpenHash }.should be_true
17
+ tree.edges.map { |e| e.label._weight }.sort.should == [0,1]
18
+
19
+ tree.remove_node! 1 # removing edge 0
20
+ tree.add_node! 3, 4 # adding edge 2
21
+ tree.add_node! 2, 5 # adding edge 3
22
+ tree.add_node! 5, 6 # adding edge 4
23
+ tree.remove_node! 6 # removing edge 4
24
+ tree.add_node! 5, 7 # adding edge 5
25
+ tree.edges.map { |e| e.label._weight }.sort.should == [1,2,3,5]
26
+ end
27
+
28
+ describe "#sort_edges" do
29
+ it "should by default return the list of edges in (increasing) order" do
30
+ tree.add_branches! 1,2, 1,3, 2,4, 1,5, 3,6, 1,7
31
+ tree.sort_edges.map { |e| e.label._weight }.should == (0..5).to_a
32
+ end
33
+
34
+ it "should accept a block to sort edges" do
35
+ # for the sake of this specs, one'd actually #reverse the default sorting ;)
36
+ tree.add_branches! 1,2, 1,3, 2,4, 1,5, 3,6, 1,7
37
+ tree.sort_edges do |edge|
38
+ -edge.label._weight
39
+ end.map { |e| e.label._weight }.should == (0..5).to_a.reverse
40
+ end
41
+ end
42
+
43
+ it "should thus allow for local edge and children ordering (directed trees only)" do
44
+ arbo.add_branches! 1,2, 1,3, 2,4, 1,5, 3,6, 1,7
45
+ arbo.sorted_arcs_from(1).map { |e| e.target }.should == [2,3,5,7]
46
+ arbo.sorted_children_of(1).should == [2,3,5,7]
47
+ end
48
+
49
+ it "should return nil when asking for nodes ordering on an undirected tree" do
50
+ tree.add_branches! 1,2, 1,3, 2,4, 1,5, 3,6, 1,7
51
+ tree.sorted_arcs_from(1).should == nil
52
+ tree.sorted_children_of(1).should == nil
53
+ end
54
+ end
55
+ end
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jumoku
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
5
4
  prerelease:
6
- segments:
7
- - 0
8
- - 2
9
- - 2
10
- version: 0.2.2
5
+ version: 0.2.3
11
6
  platform: ruby
12
7
  authors:
13
8
  - Jean-Denis Vauguet <jd@vauguet.fr>
@@ -15,7 +10,7 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2011-07-10 00:00:00 +02:00
13
+ date: 2011-07-11 00:00:00 +02:00
19
14
  default_executable:
20
15
  dependencies:
21
16
  - !ruby/object:Gem::Dependency
@@ -26,9 +21,6 @@ dependencies:
26
21
  requirements:
27
22
  - - ">="
28
23
  - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
24
  version: "0"
33
25
  type: :runtime
34
26
  version_requirements: *id001
@@ -40,54 +32,53 @@ dependencies:
40
32
  requirements:
41
33
  - - ">="
42
34
  - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
35
  version: "0"
47
36
  type: :runtime
48
37
  version_requirements: *id002
49
38
  - !ruby/object:Gem::Dependency
50
- name: plexus
39
+ name: hashery
51
40
  prerelease: false
52
41
  requirement: &id003 !ruby/object:Gem::Requirement
53
42
  none: false
54
43
  requirements:
55
44
  - - ">="
56
45
  - !ruby/object:Gem::Version
57
- hash: 3
58
- segments:
59
- - 0
60
46
  version: "0"
61
47
  type: :runtime
62
48
  version_requirements: *id003
63
49
  - !ruby/object:Gem::Dependency
64
- name: rspec
50
+ name: plexus
65
51
  prerelease: false
66
52
  requirement: &id004 !ruby/object:Gem::Requirement
67
53
  none: false
68
54
  requirements:
69
55
  - - ">="
70
56
  - !ruby/object:Gem::Version
71
- hash: 3
72
- segments:
73
- - 0
74
57
  version: "0"
75
- type: :development
58
+ type: :runtime
76
59
  version_requirements: *id004
77
60
  - !ruby/object:Gem::Dependency
78
- name: yard
61
+ name: rspec
79
62
  prerelease: false
80
63
  requirement: &id005 !ruby/object:Gem::Requirement
81
64
  none: false
82
65
  requirements:
83
66
  - - ">="
84
67
  - !ruby/object:Gem::Version
85
- hash: 3
86
- segments:
87
- - 0
88
68
  version: "0"
89
69
  type: :development
90
70
  version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: yard
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ type: :development
81
+ version_requirements: *id006
91
82
  description: Jumoku provides you with tree behaviors to mixin and tree classes to inherit from. Raw tree, common binary trees, custom trees...
92
83
  email: jd@vauguet.fr
93
84
  executables: []
@@ -107,6 +98,8 @@ files:
107
98
  - lib/jumoku/builders/raw_directed_tree.rb
108
99
  - lib/jumoku/raw_tree_node.rb
109
100
  - lib/jumoku/classes/tree_classes.rb
101
+ - lib/jumoku/strategies/edge_labeling/simple.rb
102
+ - lib/jumoku/strategies/edge_labeling.rb
110
103
  - lib/jumoku/support/support.rb
111
104
  - lib/jumoku/support/ruby_compatibility.rb
112
105
  - lib/jumoku/support/branch.rb
@@ -114,6 +107,7 @@ files:
114
107
  - spec/spec.opts
115
108
  - spec/arborescence_spec.rb
116
109
  - spec/spec_helper.rb
110
+ - spec/strategies/simple_edge_labeling.rb
117
111
  - spec/raw_directed_tree_spec.rb
118
112
  - spec/raw_undirected_tree_spec.rb
119
113
  - spec/tree_spec.rb
@@ -137,18 +131,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
131
  requirements:
138
132
  - - ">="
139
133
  - !ruby/object:Gem::Version
140
- hash: 3
141
- segments:
142
- - 0
143
134
  version: "0"
144
135
  required_rubygems_version: !ruby/object:Gem::Requirement
145
136
  none: false
146
137
  requirements:
147
138
  - - ">="
148
139
  - !ruby/object:Gem::Version
149
- hash: 3
150
- segments:
151
- - 0
152
140
  version: "0"
153
141
  requirements: []
154
142