btree 0.0.0

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/.bnsignore ADDED
@@ -0,0 +1,18 @@
1
+ # The list of files that should be ignored by Mr Bones.
2
+ # Lines that start with '#' are comments.
3
+ #
4
+ # A .gitignore file can be used instead by setting it as the ignore
5
+ # file in your Rakefile:
6
+ #
7
+ # Bones {
8
+ # ignore_file '.gitignore'
9
+ # }
10
+ #
11
+ # For a project with a C extension, the following would be a good set of
12
+ # exclude patterns (uncomment them if you want to use them):
13
+ # *.[oa]
14
+ # *~
15
+ announcement.txt
16
+ coverage
17
+ doc
18
+ pkg
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.0 / 2010-12-20
2
+
3
+ * 1 major enhancement
4
+ * Initial Release!
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ Ruby-BTree
2
+ ==========
3
+
4
+ Pure ruby implementation of a btree as described in Introduction to Algorithms by
5
+ Cormen, Leiserson, Rivest and Stein, Chapter 18.
6
+
7
+ Features
8
+ --------
9
+
10
+ * Create B-Tree of arbitrary degree (2 by default)
11
+ * Insert key value pairs using a Map like interface
12
+ * Query for values associated with keys using a Map like interface
13
+ * Keys can be any object supporting comparison operators: <, > and ==
14
+
15
+ Examples
16
+ --------
17
+
18
+ require 'btree'
19
+ tree = Btree.create # default degree = 2
20
+ tree = Btree.create(5) # degree = 5
21
+ tree['foo'] = 'foo value'
22
+ tree['bar'] = 'bar value'
23
+
24
+ puts "key 'foo' has value: #{tree['foo']}"
25
+ puts "BTree has #{tree.size} key-value pairs"
26
+
27
+ Future
28
+ ------
29
+
30
+ * Deletion is not implemented. TODO: Implement deletion
31
+ * Attempt to insert existing key raises RuntimeError.
32
+ TODO: Allow replacement of value associated with existing key instead of raising RuntimeError.
33
+ * Persist state to backing store
34
+
35
+ Install
36
+ -------
37
+
38
+ * gem install btree
39
+
40
+ Author
41
+ ------
42
+
43
+ Original author: Douglas A. Seifert (doug@dseifert.net)
44
+
45
+ License
46
+ -------
47
+
48
+ (The MIT License)
49
+
50
+ Copyright (c) 2010,2011 Douglas A. Seifert
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining
53
+ a copy of this software and associated documentation files (the
54
+ 'Software'), to deal in the Software without restriction, including
55
+ without limitation the rights to use, copy, modify, merge, publish,
56
+ distribute, sublicense, and/or sell copies of the Software, and to
57
+ permit persons to whom the Software is furnished to do so, subject to
58
+ the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be
61
+ included in all copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
64
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
65
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
66
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
67
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
68
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
69
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+
2
+ begin
3
+ require 'bones'
4
+ rescue LoadError
5
+ abort '### Please install the "bones" gem ###'
6
+ end
7
+
8
+ task :default => 'test:run'
9
+ task 'gem:release' => 'test:run'
10
+
11
+ Bones {
12
+ name 'btree'
13
+ authors 'Douglas A. Seifert'
14
+ email 'doug@dseifert.net'
15
+ url 'http://www.dseifert.net/btree'
16
+ readme_file 'README.md'
17
+ }
18
+
data/lib/btree.rb ADDED
@@ -0,0 +1,70 @@
1
+ # :main: README.md
2
+ module Btree
3
+
4
+ # :stopdoc:
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ @version ||= File.read(path('version.txt')).strip
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args, &block )
20
+ rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ if block
22
+ begin
23
+ $LOAD_PATH.unshift LIBPATH
24
+ rv = block.call
25
+ ensure
26
+ $LOAD_PATH.shift
27
+ end
28
+ end
29
+ return rv
30
+ end
31
+
32
+ # Returns the lpath for the module. If any arguments are given,
33
+ # they will be joined to the end of the path using
34
+ # <tt>File.join</tt>.
35
+ #
36
+ def self.path( *args, &block )
37
+ rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
38
+ if block
39
+ begin
40
+ $LOAD_PATH.unshift PATH
41
+ rv = block.call
42
+ ensure
43
+ $LOAD_PATH.shift
44
+ end
45
+ end
46
+ return rv
47
+ end
48
+
49
+ # Utility method used to require all files ending in .rb that lie in the
50
+ # directory below this file that has the same name as the filename passed
51
+ # in. Optionally, a specific _directory_ name can be passed in such that
52
+ # the _filename_ does not have to be equivalent to the directory.
53
+ #
54
+ def self.require_all_libs_relative_to( fname, dir = nil )
55
+ dir ||= ::File.basename(fname, '.*')
56
+ search_me = ::File.expand_path(
57
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
58
+
59
+ Dir.glob(search_me).sort.each {|rb| require rb}
60
+ end
61
+
62
+ def self.create(degree)
63
+ raise "Degree of Btree must be >= 2" if degree < 2
64
+ return Btree::Tree.new(degree)
65
+ end
66
+
67
+ end # module Btree
68
+
69
+ Btree.require_all_libs_relative_to(__FILE__)
70
+
Binary file
data/lib/btree/node.rb ADDED
@@ -0,0 +1,139 @@
1
+ class Btree::Node
2
+ attr_accessor :leaf
3
+ def initialize(degree)
4
+ @degree = degree
5
+ @keys = []
6
+ @children = []
7
+ @leaf = true
8
+ end
9
+
10
+ def dump(level = 0)
11
+ @keys.each_with_index do |key, idx|
12
+ if @children[idx]
13
+ @children[idx].dump(level + 1)
14
+ end
15
+ puts "#{level}: #{key.first}: full? #{full?} leaf? #{leaf?}"
16
+ end
17
+ (@children[@keys.size..-1] || []).each do |c|
18
+ c.dump(level+1)
19
+ end
20
+ nil
21
+ end
22
+
23
+ def add_child(node)
24
+ @children << node
25
+ end
26
+
27
+ def children
28
+ @children.dup.freeze
29
+ end
30
+
31
+ def keys
32
+ @keys.map(&:first).freeze
33
+ end
34
+
35
+ def values
36
+ @keys.map(&:last).freeze
37
+ end
38
+
39
+ def full?
40
+ size >= 2 * @degree - 1
41
+ end
42
+
43
+ def leaf?
44
+ @leaf
45
+ end
46
+
47
+ def size
48
+ @keys.size
49
+ end
50
+
51
+ def value_of(key)
52
+ i = 1
53
+ while i <= size && key > @keys[i-1].first
54
+ i += 1
55
+ end
56
+
57
+ if i <= size && key == @keys[i-1].first
58
+ return @keys[i-1].last
59
+ elsif leaf?
60
+ return nil
61
+ else
62
+ return @children[i-1].value_of(key)
63
+ end
64
+ end
65
+
66
+ def insert(key, value)
67
+ i = size - 1
68
+ #puts "INSERTING #{key} INTO NODE: #{self.inspect}"
69
+ if leaf?
70
+ raise "Duplicate key" if @keys.any?{|(k,v)| k == key } #OPTIMIZE: This is inefficient
71
+ while i >= 0 && @keys[i] && key < @keys[i].first
72
+ @keys[i+1] = @keys[i]
73
+ i -= 1
74
+ end
75
+ @keys[i+1] = [key, value]
76
+ else
77
+ while i >= 0 && @keys[i] && key < @keys[i].first
78
+ i -= 1
79
+ end
80
+ #puts " -- INSERT KEY INDEX #{i}"
81
+ if @children[i+1] && @children[i+1].full?
82
+ split(i+1)
83
+ if key > @keys[i+1].first
84
+ i += 1
85
+ end
86
+ end
87
+ @children[i+1].insert(key, value)
88
+ end
89
+ end
90
+
91
+ def split(child_idx)
92
+ raise "Invalid child index #{child_idx} in split, num_children = #{@children.size}" if child_idx < 0 || child_idx >= @children.size
93
+ #puts "SPLIT1: #{self.inspect}"
94
+ splitee = @children[child_idx]
95
+ y = Btree::Node.new(@degree)
96
+ z = Btree::Node.new(@degree)
97
+ z.leaf = splitee.leaf
98
+ (@degree-1).times do |j|
99
+ z._keys[j] = splitee._keys[j+@degree]
100
+ y._keys[j] = splitee._keys[j]
101
+ end
102
+ if !splitee.leaf?
103
+ @degree.times do |j|
104
+ z._children[j] = splitee._children[j+@degree]
105
+ y._children[j] = splitee._children[j]
106
+ end
107
+ end
108
+ mid_val = splitee._keys[@degree-1]
109
+ #puts "SPLIT2: #{self.inspect}"
110
+ (@keys.size).downto(child_idx) do |j|
111
+ @children[j+1] = @children[j]
112
+ end
113
+
114
+ @children[child_idx+1] = z
115
+ @children[child_idx] = y
116
+
117
+ #puts "SPLIT3: #{self.inspect}"
118
+
119
+ (@keys.size - 1).downto(child_idx) do |j|
120
+ @keys[j+1] = @keys[j]
121
+ end
122
+
123
+ #puts "SPLIT4: #{self.inspect}"
124
+
125
+ @keys[child_idx] = mid_val
126
+ #puts "SPLIT5: #{self.inspect}"
127
+ end
128
+
129
+ protected
130
+
131
+ def _keys
132
+ @keys
133
+ end
134
+
135
+ def _children
136
+ @children
137
+ end
138
+
139
+ end
data/lib/btree/tree.rb ADDED
@@ -0,0 +1,46 @@
1
+ class Btree::Tree
2
+ attr_reader :root, :degree, :size
3
+
4
+ # Creates a BTree of degree 2 by default. Keys
5
+ # Must support being compared using >, < and == methods.
6
+ def initialize(degree = 2)
7
+ @degree = degree
8
+ @root = Btree::Node.new(@degree)
9
+ @root.leaf = true
10
+ @size = 0
11
+ end
12
+
13
+ # Insert a key-value pair into the btree
14
+ def insert(key, value = nil)
15
+ node = @root
16
+ if node.full?
17
+ @root = Btree::Node.new(@degree)
18
+ @root.leaf = false
19
+ @root.add_child(node)
20
+ @root.split(@root.children.size - 1)
21
+ #puts "After split, root = #{@root.inspect}"
22
+ # split child(@root, 1)
23
+ node = @root
24
+ end
25
+ node.insert(key, value)
26
+ @size += 1
27
+ return self
28
+ end
29
+
30
+ # puts internal state
31
+ def dump
32
+ @root.dump
33
+ end
34
+
35
+ # Get value associated with the specified key
36
+ def value_of(key)
37
+ @root.value_of(key)
38
+ end
39
+
40
+ # Support map like access
41
+ alias_method :"[]", :value_of
42
+
43
+ # Support map like key-value setting
44
+ alias_method :"[]=", :insert
45
+
46
+ end
@@ -0,0 +1,110 @@
1
+ require 'test/unit'
2
+ require 'btree'
3
+ require 'shoulda'
4
+
5
+ class TestBtree < Test::Unit::TestCase
6
+ def test_insert_notfull
7
+ t = Btree.create(5)
8
+ t.insert(5, "5")
9
+ assert_equal 1, t.root.size
10
+ assert !t.root.full?
11
+ assert_equal 5, t.degree
12
+ assert_equal 1, t.size
13
+ end
14
+
15
+ def test_degree_too_small
16
+ assert_raises(RuntimeError) do
17
+ Btree.create(1)
18
+ end
19
+ end
20
+
21
+ def test_insert_duplicate_key
22
+ t = Btree.create(2)
23
+ t.insert(1, "1")
24
+ assert_raises(RuntimeError) do
25
+ t.insert(1, "1")
26
+ end
27
+ end
28
+
29
+ def test_value_of
30
+ t = Btree.create(5)
31
+ t.insert(1, "foo")
32
+ t.insert(5, "bar")
33
+ t.insert(7, "baz")
34
+ t.insert(3, "findme")
35
+ assert_equal "findme", t.value_of(3)
36
+ assert_equal "baz", t.value_of(7)
37
+ assert_equal "bar", t.value_of(5)
38
+ assert_equal "foo", t.value_of(1)
39
+ assert_nil t.value_of(11)
40
+ assert_equal 4, t.size
41
+ end
42
+
43
+ def test_fill_root
44
+ t = Btree.create(2)
45
+ 3.times {|n| t.insert(n, n.to_s)}
46
+ assert t.root.full?
47
+ assert_equal [0,1,2], t.root.keys
48
+ assert_equal ['0','1','2'], t.root.values
49
+ assert_equal 3, t.size
50
+ end
51
+
52
+ context 'full root' do
53
+ setup do
54
+ @t = Btree.create(2)
55
+ 3.times {|n| @t.insert(n*2, n.to_s)}
56
+ end
57
+ should "be able to insert at end" do
58
+ @t.insert(10, "10")
59
+ assert_equal "10", @t.value_of(10)
60
+ assert_equal "0", @t.value_of(0)
61
+ assert_equal "1", @t.value_of(2)
62
+ assert_equal "2", @t.value_of(4)
63
+ assert_equal 4, @t.size
64
+ end
65
+ should "be able to insert at front" do
66
+ @t.insert(-1, "10")
67
+ assert_equal "10", @t.value_of(-1)
68
+ assert_equal "0", @t.value_of(0)
69
+ assert_equal "1", @t.value_of(2)
70
+ assert_equal "2", @t.value_of(4)
71
+ assert_equal 4, @t.size
72
+ end
73
+ should "be able to insert in the middle" do
74
+ @t.insert(3, "10")
75
+ assert_equal "10", @t.value_of(3)
76
+ assert_equal "0", @t.value_of(0)
77
+ assert_equal "1", @t.value_of(2)
78
+ assert_equal "2", @t.value_of(4)
79
+ assert_equal 4, @t.size
80
+ end
81
+ context 'full last child' do
82
+ setup do
83
+ 2.times {|n| @t.insert(n*2 + 10, "foo") }
84
+ end
85
+ should "have full last child" do
86
+ assert @t.root.children.last.full?, "Last child of root should be full"
87
+ assert_equal 5, @t.size
88
+ end
89
+ should "be able to add to end" do
90
+ @t.insert(100, "YEAH!")
91
+ assert_equal "YEAH!", @t.value_of(100)
92
+ assert_equal 6, @t.size
93
+ end
94
+ should "be able to insert many" do
95
+ 10.times { |n| @t.insert((n+1) * 100, "YEAH!") }
96
+ assert_equal "YEAH!", @t.value_of(100)
97
+ assert_equal 15, @t.size
98
+ end
99
+ end
100
+ context 'second split of root' do
101
+ setup do
102
+ 6.times {|n| @t.insert(n*2 + 10, "foo") }
103
+ end
104
+ should "work" do
105
+ assert_equal 'foo', @t.value_of(10)
106
+ assert_equal 9, @t.size
107
+ end
108
+ end
109
+ end
110
+ end
data/version.txt ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: btree
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Douglas A. Seifert
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-12-20 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bones
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 27
30
+ segments:
31
+ - 3
32
+ - 5
33
+ - 4
34
+ version: 3.5.4
35
+ type: :development
36
+ version_requirements: *id001
37
+ description: |-
38
+ Pure ruby implementation of a btree as described in Introduction to Algorithms by
39
+ Cormen, Leiserson, Rivest and Stein, Chapter 18.
40
+ email: doug@dseifert.net
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ extra_rdoc_files:
46
+ - History.txt
47
+ - lib/btree/.node.rb.swp
48
+ - version.txt
49
+ files:
50
+ - .bnsignore
51
+ - History.txt
52
+ - README.md
53
+ - Rakefile
54
+ - lib/btree.rb
55
+ - lib/btree/.node.rb.swp
56
+ - lib/btree/node.rb
57
+ - lib/btree/tree.rb
58
+ - test/test_btree.rb
59
+ - version.txt
60
+ has_rdoc: true
61
+ homepage: http://www.dseifert.net/btree
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --main
67
+ - README.md
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project: btree
91
+ rubygems_version: 1.3.7
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Pure ruby implementation of a btree as described in Introduction to Algorithms by Cormen, Leiserson, Rivest and Stein, Chapter 18.
95
+ test_files:
96
+ - test/test_btree.rb