btree 1.0.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 64a14900f4f059a6341312729643c101a88d19ea
4
- data.tar.gz: da8ab66ed5bdf738658f2ef7ecb1421c9d58d1dc
2
+ SHA256:
3
+ metadata.gz: 5a5640c8a1cddfdfb4f56acbc605aaa5722ab154605f133a8dfa8b089cc32e1c
4
+ data.tar.gz: edf0f1348d367ddacb3337d42df137201064c981b5becc84ab6a0b1368990217
5
5
  SHA512:
6
- metadata.gz: 0a1929ec3cb16b36af5ef5cbc1546bf59652f4aeddf6765b9ff70c7ea502c77827e29fb41ce7b556d7bb93c3cf6f293b46ce1ae06dcd79551cfe4b8e5d74924c
7
- data.tar.gz: 26fc63048a49802026ba22661f025fc904d295ff0198c277dfd410650bbf539efb2477203e778d13e3328af2ed0247d6aeee1083d39bfaa5d88d545e5eb4d7f3
6
+ metadata.gz: a7ded3fe792d31605f5a115402419f819c0bebfc72d38ac50fac86300bd595b871be8d73a19d538a75445a76579b327bdc2884f38e5606f94ca8761df976a26d
7
+ data.tar.gz: 0cee704dabf324cddfab2e6edac454fe7698cfdd8b936f013969aec0b4b22e75ef6ac2ca272ab2d04313b0bdd5b1b6d35ce0d4e54f5eb6d6789988c95d64121e
data/Gemfile CHANGED
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
  source "https://rubygems.org"
3
- #repo_name "seifertd/Ruby-BTree"
4
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
5
3
 
6
- # gem "rails"
7
-
8
- # Added at 2017-11-09 08:39:45 -0800 by doug:
9
- gem "rake", "~> 12.2", :group => [:development, :test]
10
-
11
- # Added at 2017-11-09 08:40:02 -0800 by doug:
12
- gem "bones", "~> 3.8", :group => [:development, :test]
13
-
14
- # Added at 2017-11-09 08:40:12 -0800 by doug:
15
- gem "shoulda", "~> 3.5", :group => [:development, :test]
4
+ # Runtime dependencies come from btree.gemspec, which declares none.
5
+ gemspec
6
+
7
+ # shoulda-context supplies the context/should DSL the tests use. The shoulda
8
+ # meta-gem would also pull in shoulda-matchers, whose only purpose is Rails
9
+ # model assertions this project has no use for, and which drags in the whole
10
+ # activesupport tree behind it.
11
+ group :development, :test do
12
+ gem "rake", "~> 13"
13
+ gem "minitest", "~> 6.0"
14
+ gem "shoulda-context", "~> 2.0"
15
+ end
data/Gemfile.lock CHANGED
@@ -1,41 +1,36 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ btree (1.0.1)
5
+
1
6
  GEM
2
7
  remote: https://rubygems.org/
3
8
  specs:
4
- activesupport (5.1.4)
5
- concurrent-ruby (~> 1.0, >= 1.0.2)
6
- i18n (~> 0.7)
7
- minitest (~> 5.1)
8
- tzinfo (~> 1.1)
9
- bones (3.8.4)
10
- little-plugger (~> 1.1)
11
- loquacious (~> 1.9)
12
- rake (~> 12.0)
13
- rdoc (~> 5.0)
14
- concurrent-ruby (1.0.5)
15
- i18n (0.9.1)
16
- concurrent-ruby (~> 1.0)
17
- little-plugger (1.1.4)
18
- loquacious (1.9.1)
19
- minitest (5.10.3)
20
- rake (12.2.1)
21
- rdoc (5.1.0)
22
- shoulda (3.5.0)
23
- shoulda-context (~> 1.0, >= 1.0.1)
24
- shoulda-matchers (>= 1.4.1, < 3.0)
25
- shoulda-context (1.2.2)
26
- shoulda-matchers (2.8.0)
27
- activesupport (>= 3.0.0)
28
- thread_safe (0.3.6)
29
- tzinfo (1.2.4)
30
- thread_safe (~> 0.1)
9
+ drb (2.2.3)
10
+ minitest (6.0.6)
11
+ drb (~> 2.0)
12
+ prism (~> 1.5)
13
+ prism (1.9.0)
14
+ rake (13.4.2)
15
+ shoulda-context (2.0.0)
31
16
 
32
17
  PLATFORMS
33
18
  ruby
19
+ x86_64-linux
34
20
 
35
21
  DEPENDENCIES
36
- bones (~> 3.8)
37
- rake (~> 12.2)
38
- shoulda (~> 3.5)
22
+ btree!
23
+ minitest (~> 6.0)
24
+ rake (~> 13)
25
+ shoulda-context (~> 2.0)
26
+
27
+ CHECKSUMS
28
+ btree (1.0.1)
29
+ drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
30
+ minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
31
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
32
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
33
+ shoulda-context (2.0.0) sha256=7adf45342cd800f507d2a053658cb1cce2884b616b26004d39684b912ea32c34
39
34
 
40
35
  BUNDLED WITH
41
- 1.15.3
36
+ 4.0.17
data/History.txt CHANGED
@@ -1,3 +1,22 @@
1
+ == 1.0.1 / 2026-07-23
2
+
3
+ * 2 bug fixes
4
+ * Range queries now visit every subtree that can contain an in-range key.
5
+ Previously the descent stopped at the first key at or above the range end,
6
+ so values held in later subtrees were silently omitted from the result.
7
+ * Inserting a key that a node split had promoted into an internal node now
8
+ raises "Duplicate key". Previously the duplicate check ran only at the
9
+ leaf, so such a key was descended past and inserted a second time, leaving
10
+ one copy unreachable in a subtree.
11
+
12
+ * 1 minor enhancement
13
+ * Nodes locate keys by a hybrid linear/binary search, scanning small nodes
14
+ and using bsearch on large ones.
15
+
16
+ == 1.0.0 / 2017-11-09
17
+
18
+ * Initial gem release.
19
+
1
20
  == 0.0.0 / 2010-12-20
2
21
 
3
22
  * 1 major enhancement
data/Rakefile CHANGED
@@ -1,19 +1,177 @@
1
+ require 'rake/testtask'
2
+ require 'rake/clean'
3
+ require 'rubygems/package_task'
1
4
 
2
- begin
3
- require 'bones'
5
+ SPEC = Gem::Specification.load('btree.gemspec')
6
+ VERSION = SPEC.version.to_s
7
+ GEM_FILE = "pkg/#{SPEC.full_name}.gem"
8
+
9
+ CLOBBER.include('pkg', 'doc')
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib' << 'test'
13
+ t.test_files = FileList['test/test_*.rb']
14
+ end
15
+
16
+ task :default => :test
17
+
18
+ Gem::PackageTask.new(SPEC) do |pkg|
19
+ pkg.need_zip = false
20
+ pkg.need_tar = false
21
+ end
22
+
23
+ desc 'Build the gem into pkg/'
24
+ task :build => :package
25
+
26
+ desc 'Install the built gem locally'
27
+ task :install => :build do
28
+ sh 'gem', 'install', GEM_FILE
29
+ end
30
+
31
+ # Shells out with Bundler's environment stripped. Anything that inspects the
32
+ # gem as an end user would -- installing it, requiring it -- has to run outside
33
+ # this project's bundle, or it picks up the working tree instead of the package.
34
+ def unbundled(*cmd)
35
+ require 'bundler'
36
+ Bundler.with_unbundled_env { sh(*cmd) }
4
37
  rescue LoadError
5
- abort '### Please install the "bones" gem ###'
38
+ sh(*cmd)
39
+ end
40
+
41
+ def rubygems_versions
42
+ require 'open-uri'
43
+ require 'json'
44
+ JSON.parse(URI.open("https://rubygems.org/api/v1/versions/#{SPEC.name}.json").read)
45
+ rescue OpenURI::HTTPError, SocketError, JSON::ParserError => e
46
+ warn "WARNING: could not reach rubygems.org (#{e.class}: #{e.message})."
47
+ nil
6
48
  end
7
49
 
8
- task :default => 'test:run'
9
- task 'gem:release' => 'test:run'
50
+ namespace :release do
51
+ desc 'Check the working tree, metadata and credentials are fit to release'
52
+ task :check do
53
+ problems = []
54
+
55
+ problems << 'working tree is dirty; commit or stash first' unless
56
+ `git status --porcelain`.strip.empty?
57
+
58
+ # The tag has to point at a commit that exists somewhere other than this
59
+ # laptop, otherwise a published gem can be built from work nobody else has.
60
+ if `git branch -r --contains HEAD 2>/dev/null`.strip.empty?
61
+ problems << 'HEAD is not on any remote branch; push it before releasing'
62
+ end
63
+
64
+ problems << "tag v#{VERSION} already exists; bump version.txt" if
65
+ `git tag -l v#{VERSION}`.strip == "v#{VERSION}"
66
+
67
+ problems << "History.txt has no entry for #{VERSION}" unless
68
+ File.read('History.txt').start_with?("== #{VERSION} ")
10
69
 
11
- Bones {
12
- name 'btree'
13
- authors 'Douglas A. Seifert'
14
- email 'doug@dseifert.net'
15
- url 'https://github.com/seifertd/Ruby-BTree'
16
- readme_file 'README.md'
17
- exclude ['.bnsignore', '.gitignore', '.ruby-gemset', '.ruby-version', '.travis.yml', 'vendor', '.git']
18
- }
70
+ # A version number is the one genuinely unrecoverable resource in a
71
+ # release: RubyGems refuses to re-push it even after a yank.
72
+ if (published = rubygems_versions)
73
+ problems << "#{SPEC.name} #{VERSION} is already on rubygems.org" if
74
+ published.any? {|v| v['number'] == VERSION }
75
+ end
19
76
 
77
+ # Surfaced now rather than as a 403 after the tag has been pushed.
78
+ # Ask RubyGems where the credentials file lives rather than assuming
79
+ # ~/.gem/credentials: modern installs use the XDG path
80
+ # (~/.local/share/gem/credentials), and hardcoding the old one reports a
81
+ # missing key that is really there.
82
+ require 'rubygems/config_file'
83
+ creds = Gem.configuration.credentials_path
84
+ unless ENV['GEM_HOST_API_KEY'] || File.exist?(creds)
85
+ problems << "no rubygems credentials at #{creds}; run `gem signin` (the key needs the push_rubygem scope)"
86
+ end
87
+
88
+ branch = `git rev-parse --abbrev-ref HEAD`.strip
89
+ warn "WARNING: releasing from #{branch}, not master." unless branch == 'master'
90
+
91
+ unless problems.empty?
92
+ abort "ERROR: not ready to release #{SPEC.name} #{VERSION}:\n" +
93
+ problems.map {|p| " - #{p}" }.join("\n")
94
+ end
95
+
96
+ puts "OK: #{SPEC.name} #{VERSION} passes pre-release checks."
97
+ end
98
+
99
+ desc 'Build the gem and prove the built artifact actually works'
100
+ task :verify => :repackage do
101
+ require 'tmpdir'
102
+
103
+ puts "Packaged #{GEM_FILE}:"
104
+ Gem::Package.new(GEM_FILE).spec.files.each {|f| puts " #{f}" }
105
+
106
+ # Installing into a throwaway GEM_HOME is the only check that covers what
107
+ # was actually packaged rather than what is sitting in the working tree --
108
+ # a file missing from the manifest looks fine locally and breaks on install.
109
+ Dir.mktmpdir do |dir|
110
+ unbundled 'gem', 'install', '--no-document', '--install-dir', dir, GEM_FILE
111
+
112
+ script = <<~RUBY
113
+ gem 'btree'
114
+ require 'btree'
115
+ raise "version mismatch: \#{Btree.version} != #{VERSION}" unless Btree.version == '#{VERSION}'
116
+ t = Btree.create(3)
117
+ (1..50).each {|i| t[i] = i.to_s }
118
+ got = t[10..14]
119
+ raise "bad range query: \#{got.inspect}" unless got == %w[10 11 12 13 14]
120
+ raise "bad lookup" unless t[37] == '37'
121
+ puts "smoke test passed on the installed gem"
122
+ RUBY
123
+
124
+ Dir.chdir(dir) do
125
+ unbundled({'GEM_HOME' => dir, 'RUBYOPT' => nil}, RbConfig.ruby, '-e', script)
126
+ end
127
+ end
128
+ end
129
+
130
+ desc 'Tag the release and push the tag to origin (reversible)'
131
+ task :tag do
132
+ sh 'git', 'tag', '-a', "v#{VERSION}", '-m', "#{SPEC.name} #{VERSION}"
133
+ sh 'git', 'push', 'origin', "v#{VERSION}"
134
+ puts "Tagged v#{VERSION}. To undo: git tag -d v#{VERSION} && git push origin :v#{VERSION}"
135
+ end
136
+
137
+ # repackage, not build: the packaged gem is a file task, so an existing
138
+ # pkg/*.gem newer than its sources satisfies :build without rebuilding. Fine
139
+ # locally, wrong when the next step is irreversible.
140
+ desc 'Push the gem to rubygems.org (IRREVERSIBLE)'
141
+ task :push => :repackage do
142
+ puts
143
+ puts "About to push #{GEM_FILE} to rubygems.org."
144
+ puts "This cannot be undone: #{SPEC.name} #{VERSION} can never be re-pushed,"
145
+ puts 'even if it is yanked afterwards.'
146
+
147
+ unless ENV['FORCE'] == '1'
148
+ abort 'ERROR: refusing to push from a non-interactive shell without FORCE=1.' unless $stdin.tty?
149
+ print "Type the version (#{VERSION}) to confirm: "
150
+ abort 'Aborted.' unless $stdin.gets.to_s.strip == VERSION
151
+ end
152
+
153
+ sh 'gem', 'push', GEM_FILE
154
+ end
155
+
156
+ desc 'Confirm the version is live on rubygems.org'
157
+ task :confirm do
158
+ published = rubygems_versions
159
+ if published&.any? {|v| v['number'] == VERSION }
160
+ puts "Confirmed: #{SPEC.name} #{VERSION} is live on rubygems.org."
161
+ else
162
+ warn "WARNING: #{SPEC.name} #{VERSION} is not visible on rubygems.org yet (indexing can lag)."
163
+ end
164
+ end
165
+ end
166
+
167
+ desc 'Full release: check, test, verify the built gem, tag, then push'
168
+ task :release do
169
+ # Ordered so that everything reversible happens first. The tag is cheap to
170
+ # delete; the push is permanent, so it goes last and only after the packaged
171
+ # gem has been installed and exercised.
172
+ %w[release:check test release:verify release:tag release:push release:confirm].each do |t|
173
+ puts "\n==> #{t}"
174
+ Rake::Task[t].invoke
175
+ end
176
+ puts "\nReleased #{SPEC.name} #{VERSION}."
177
+ end
data/btree.gemspec ADDED
@@ -0,0 +1,44 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'btree'
5
+ # version.txt stays the single source of truth: Btree.version reads it back
6
+ # at runtime, so it has to ship in the gem regardless.
7
+ s.version = File.read(File.expand_path('version.txt', __dir__)).strip
8
+ s.authors = ['Douglas A. Seifert']
9
+ s.email = 'doug@dseifert.net'
10
+ s.homepage = 'https://github.com/seifertd/Ruby-BTree'
11
+ s.license = 'MIT'
12
+
13
+ s.summary = 'Pure ruby implementation of a btree'
14
+ s.description = <<~DESC
15
+ Pure ruby implementation of a btree as described in Introduction to
16
+ Algorithms by Cormen, Leiserson, Rivest and Stein, Chapter 18.
17
+ DESC
18
+
19
+ # Array#bsearch_index, used by Node#key_index, is the oldest thing here that
20
+ # is not universally available. CI only exercises 3.2 and up, because that is
21
+ # the floor imposed by the development gems, not by the library.
22
+ s.required_ruby_version = '>= 2.3.0'
23
+
24
+ # Rejecting dotfiles drops .github/, .gitignore and the .ruby-* files, which
25
+ # is the same set the old Bones exclude list named by hand. Building outside
26
+ # a git checkout is not supported, which matches how this gem is released.
27
+ s.files = `git ls-files -z`.split("\x0").reject {|f| f.start_with?('.') }
28
+
29
+ s.require_paths = ['lib']
30
+ s.extra_rdoc_files = ['History.txt', 'README.md']
31
+ s.rdoc_options = ['--main', 'README.md']
32
+
33
+ s.metadata = {
34
+ 'homepage_uri' => s.homepage,
35
+ 'source_code_uri' => s.homepage,
36
+ 'changelog_uri' => "#{s.homepage}/blob/master/History.txt",
37
+ 'bug_tracker_uri' => "#{s.homepage}/issues",
38
+
39
+ # Requires that whoever pushes this gem has MFA enabled on their
40
+ # rubygems.org account, so a leaked API key is not enough on its own.
41
+ # This needs MFA turned on for the account first, or the push is rejected.
42
+ 'rubygems_mfa_required' => 'true'
43
+ }
44
+ end
data/lib/btree/node.rb CHANGED
@@ -1,4 +1,16 @@
1
1
  class Btree::Node
2
+
3
+ # Nodes holding at most this many keys are searched by a straight scan
4
+ # rather than by bsearch, whose per-probe block dispatch costs more than the
5
+ # comparisons it saves on a short array. The measured crossover is around
6
+ # 23 keys and the curves are within a couple of percent of each other from
7
+ # roughly 19 to 23, so the exact value here matters little. Note that a
8
+ # degree of 12 or less can never exceed the limit (a node holds at most
9
+ # 2*degree - 1 keys), and that a node under a larger degree still scans
10
+ # while it is sparsely filled.
11
+ LINEAR_SCAN_LIMIT = 20
12
+ private_constant :LINEAR_SCAN_LIMIT
13
+
2
14
  def initialize(degree)
3
15
  @degree = degree
4
16
  @keys = []
@@ -46,17 +58,27 @@ class Btree::Node
46
58
  @keys.size
47
59
  end
48
60
 
61
+ # Values whose keys fall in the range, in key order. Subtrees that cannot
62
+ # contain an in-range key are never visited.
49
63
  def values_of(range)
50
64
 
51
65
  result = Array.new
52
-
53
- i = 1
54
- while i <= size && range.end >= @keys[i-1].first
55
- if range.cover? @keys[i-1].first
56
- result << @keys[i-1].last
57
- child = @children[i-1].values_of(range) unless leaf?
58
- result += child if child
59
- end
66
+ lo = range.begin
67
+ hi = range.end
68
+
69
+ # Skip the keys, and the subtrees between them, that sort entirely below
70
+ # the range. When every key is below it, this lands on the rightmost
71
+ # child, which may still hold in-range keys.
72
+ i = lo ? key_index(lo) : 0
73
+
74
+ result += @children[i].values_of(range) if !leaf? && @children[i]
75
+
76
+ while i < size
77
+ key = @keys[i].first
78
+ # Everything from here on sorts above the range.
79
+ break if hi && (range.exclude_end? ? key >= hi : key > hi)
80
+ result << @keys[i].last if range.cover? key
81
+ result += @children[i+1].values_of(range) if !leaf? && @children[i+1]
60
82
  i += 1
61
83
  end
62
84
 
@@ -69,47 +91,36 @@ class Btree::Node
69
91
 
70
92
  return values_of(key) if key.kind_of? Range
71
93
 
72
- i = 1
73
- while i <= size && key > @keys[i-1].first
74
- i += 1
75
- end
76
-
77
- #puts "Getting value of key #{key}, i = #{i}, keys = #{@keys.inspect}, leaf? #{leaf?}, numchildren: #{@children.size}"
94
+ i = key_index(key)
78
95
 
79
- if i <= size && key == @keys[i-1].first
80
- #puts "Found key: #{key.inspect}"
81
- return @keys[i-1].last
96
+ if i < size && key == @keys[i].first
97
+ return @keys[i].last
82
98
  elsif leaf?
83
- #puts "We are a leaf, no more children, so val is nil"
84
99
  return nil
85
100
  else
86
- #puts "Looking into child #{i}"
87
- return @children[i-1].value_of(key)
101
+ return @children[i].value_of(key)
88
102
  end
89
103
  end
90
104
 
91
105
  def insert(key, value)
92
- i = size - 1
93
- #puts "INSERTING #{key} INTO NODE: #{self.inspect}"
106
+ # The slot the key would occupy is also the slot it would already occupy if
107
+ # it were present, so locating it and rejecting a duplicate are one step.
108
+ # Checked at every node on the way down, not just at the leaf: a key that a
109
+ # split has promoted into an internal node would otherwise be descended
110
+ # past and inserted a second time, hiding one copy in a subtree.
111
+ i = key_index(key)
112
+ raise "Duplicate key" if i < size && @keys[i].first == key
113
+
94
114
  if leaf?
95
- raise "Duplicate key" if @keys.any?{|(k,v)| k == key } #OPTIMIZE: This is inefficient
96
- while i >= 0 && @keys[i] && key < @keys[i].first
97
- @keys[i+1] = @keys[i]
98
- i -= 1
99
- end
100
- @keys[i+1] = [key, value]
115
+ @keys.insert(i, [key, value])
101
116
  else
102
- while i >= 0 && @keys[i] && key < @keys[i].first
103
- i -= 1
117
+ if @children[i] && @children[i].full?
118
+ split(i)
119
+ # The split just promoted a key into this node, after the check above.
120
+ raise "Duplicate key" if @keys[i].first == key
121
+ i += 1 if key > @keys[i].first
104
122
  end
105
- #puts " -- INSERT KEY INDEX #{i}"
106
- if @children[i+1] && @children[i+1].full?
107
- split(i+1)
108
- if key > @keys[i+1].first
109
- i += 1
110
- end
111
- end
112
- @children[i+1].insert(key, value)
123
+ @children[i].insert(key, value)
113
124
  end
114
125
  end
115
126
 
@@ -160,4 +171,21 @@ class Btree::Node
160
171
  @children
161
172
  end
162
173
 
174
+ private
175
+
176
+ # Index of the first key that does not sort below the given one, or size if
177
+ # every key sorts below it. For an internal node that is also the index of
178
+ # the child to descend into. Both branches test only < so that keys need
179
+ # only support <, > and == -- bsearch's natural k >= key would widen that.
180
+ def key_index(key)
181
+ n = size
182
+ if n <= LINEAR_SCAN_LIMIT
183
+ i = 0
184
+ i += 1 while i < n && @keys[i].first < key
185
+ i
186
+ else
187
+ @keys.bsearch_index {|(k, _)| !(k < key) } || n
188
+ end
189
+ end
190
+
163
191
  end
data/test/test_btree.rb CHANGED
@@ -1,8 +1,50 @@
1
1
  require 'minitest/autorun'
2
2
  require 'btree'
3
- require 'shoulda'
3
+ require 'shoulda/context'
4
+
5
+ class TestBtree < Minitest::Test
6
+ # Degree 2 tree with a real internal node:
7
+ # root [10, 20]
8
+ # children [5,6,7] [12,17] [30]
9
+ def multi_level_tree
10
+ t = Btree.create(2)
11
+ [10, 20, 5, 6, 12, 30, 7, 17].each {|k| t.insert(k, k.to_s)}
12
+ t
13
+ end
14
+
15
+ def test_range_query_spans_multiple_children
16
+ t = multi_level_tree
17
+ assert_equal %w[5 6 7 10 12 17], t.value_of(5..17)
18
+ end
19
+
20
+ def test_range_query_finds_keys_in_rightmost_child
21
+ t = Btree.create(2)
22
+ (1..10).each {|k| t.insert(k, k.to_s)}
23
+ assert_equal %w[8 9 10], t.value_of(8..10)
24
+ end
25
+
26
+ def test_insert_duplicate_of_internal_key
27
+ t = multi_level_tree
28
+ internal_key = t.root.keys.first
29
+ refute t.root.leaf?, "fixture should have an internal root"
30
+ assert_raises(RuntimeError) do
31
+ t.insert(internal_key, "duplicate")
32
+ end
33
+ end
34
+
35
+ def test_duplicate_of_internal_key_does_not_corrupt_size
36
+ t = multi_level_tree
37
+ internal_key = t.root.keys.first
38
+ size_before = t.size
39
+ begin
40
+ t.insert(internal_key, "duplicate")
41
+ rescue RuntimeError
42
+ # expected once duplicate detection descends past the leaves
43
+ end
44
+ assert_equal size_before, t.size
45
+ assert_equal internal_key.to_s, t.value_of(internal_key)
46
+ end
4
47
 
5
- class TestBtree < MiniTest::Test
6
48
  def test_insert_notfull
7
49
  t = Btree.create(5)
8
50
  t.insert(5, "5")
data/version.txt CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.0.1
metadata CHANGED
@@ -1,52 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: btree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Douglas A. Seifert
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2017-11-09 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bones
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 3.8.4
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: 3.8.4
27
- description: "Pure ruby implementation of a btree as described in Introduction to
28
- Algorithms by \nCormen, Leiserson, Rivest and Stein, Chapter 18."
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: |
13
+ Pure ruby implementation of a btree as described in Introduction to
14
+ Algorithms by Cormen, Leiserson, Rivest and Stein, Chapter 18.
29
15
  email: doug@dseifert.net
30
16
  executables: []
31
17
  extensions: []
32
18
  extra_rdoc_files:
33
19
  - History.txt
20
+ - README.md
34
21
  files:
35
22
  - Gemfile
36
23
  - Gemfile.lock
37
24
  - History.txt
38
25
  - README.md
39
26
  - Rakefile
27
+ - btree.gemspec
40
28
  - lib/btree.rb
41
29
  - lib/btree/node.rb
42
30
  - lib/btree/tree.rb
43
- - lib/insert_alot.rb
44
31
  - test/test_btree.rb
45
32
  - version.txt
46
33
  homepage: https://github.com/seifertd/Ruby-BTree
47
- licenses: []
48
- metadata: {}
49
- post_install_message:
34
+ licenses:
35
+ - MIT
36
+ metadata:
37
+ homepage_uri: https://github.com/seifertd/Ruby-BTree
38
+ source_code_uri: https://github.com/seifertd/Ruby-BTree
39
+ changelog_uri: https://github.com/seifertd/Ruby-BTree/blob/master/History.txt
40
+ bug_tracker_uri: https://github.com/seifertd/Ruby-BTree/issues
41
+ rubygems_mfa_required: 'true'
50
42
  rdoc_options:
51
43
  - "--main"
52
44
  - README.md
@@ -56,18 +48,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
48
  requirements:
57
49
  - - ">="
58
50
  - !ruby/object:Gem::Version
59
- version: '0'
51
+ version: 2.3.0
60
52
  required_rubygems_version: !ruby/object:Gem::Requirement
61
53
  requirements:
62
54
  - - ">="
63
55
  - !ruby/object:Gem::Version
64
56
  version: '0'
65
57
  requirements: []
66
- rubyforge_project: btree
67
- rubygems_version: 2.6.12
68
- signing_key:
58
+ rubygems_version: 4.0.17
69
59
  specification_version: 4
70
- summary: Pure ruby implementation of a btree as described in Introduction to Algorithms
71
- by Cormen, Leiserson, Rivest and Stein, Chapter 18.
72
- test_files:
73
- - test/test_btree.rb
60
+ summary: Pure ruby implementation of a btree
61
+ test_files: []
data/lib/insert_alot.rb DELETED
@@ -1,7 +0,0 @@
1
- require "./lib/btree"
2
-
3
- t = Btree.create(2)
4
- 45.times {|n| t.insert n, n*n}
5
- puts "tree size = #{t.size}"
6
- puts "Value of 0: #{t.value_of(0).inspect}"
7
- t.dump