jumoku 0.2.2 → 0.2.3

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