hipe-simplebtree 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.txt +34 -7
- data/Thorfile +100 -7
- data/lib/hipe-simplebtree.rb +411 -27
- data/spec/public/simple_spec.rb +12 -4
- data/test.rb +787 -0
- metadata +12 -4
data/README.txt
CHANGED
@@ -1,9 +1,36 @@
|
|
1
|
-
This
|
1
|
+
This was initially just for trying to get my first gem to work. It then
|
2
|
+
became an attempt to make a pure-ruby version of Takuma Ozawa's "rbtree"
|
3
|
+
gem. (Although note that this is not an rbtree, just a simple btree.)
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
|
6
|
+
TO PLAY WITH THIS AS A GEM TO DEVELOP WITH:
|
7
|
+
|
8
|
+
If you have checked this out from http://github.com/hipe/hipe-simplebtree
|
9
|
+
(the "sources") from with the project folder (of this uncompiled gem,)
|
10
|
+
$ thor default:gemspec # makes the gemspec file from the thor script
|
11
|
+
$ thor default:build # makes the *.gem file
|
12
|
+
$ sudo thor default:install # installs the gem on your system
|
13
|
+
|
14
|
+
(if you don't have thor, it is a gem and it is like rake.)
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
TO RUN THE UNIT TESTS/SPECS ON THIS:
|
19
|
+
|
20
|
+
There are two test suites to run on this.
|
21
|
+
|
22
|
+
1. One is the Test::Unit tests copy-pasted from Takuma Ozawa's 'rbtree' gem.
|
23
|
+
These tests were used to ensure that this module behaves exactly as his does
|
24
|
+
(in as far as the tests specify ;) " Run it with:
|
25
|
+
$ ruby test.rb
|
26
|
+
|
27
|
+
2. The other test suite is the one I wrote for any new features or fun tests
|
28
|
+
I wanted to do on my own. Run them with:
|
29
|
+
$ rake spec
|
30
|
+
(you will need the rake gem and the rspec gem.)
|
31
|
+
|
32
|
+
If any tests fail, please email me. ("mark" and then a dot and then "meves"
|
33
|
+
at google's popular mail service.)
|
34
|
+
|
8
35
|
|
9
|
-
|
36
|
+
Special Thanks to Yoko and Ozawa.
|
data/Thorfile
CHANGED
@@ -3,20 +3,27 @@ module GemHelpers
|
|
3
3
|
|
4
4
|
def generate_gemspec
|
5
5
|
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
|
6
|
-
require "
|
6
|
+
require "hipe-simplebtree"
|
7
7
|
|
8
8
|
Gem::Specification.new do |s|
|
9
9
|
s.name = 'hipe-simplebtree'
|
10
|
-
s.version = Hipe::
|
10
|
+
s.version = Hipe::SimpleBTree::VERSION
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
12
|
-
s.
|
12
|
+
s.authors = ["Mark Meves"]
|
13
13
|
s.email = "mark.meves@gmail.com"
|
14
14
|
s.homepage = "http://github.com/hipe/hipe-simplebtree"
|
15
|
-
s.date = %q{2009-11-
|
16
|
-
s.summary = %q{
|
15
|
+
s.date = %q{2009-11-23}
|
16
|
+
s.summary = %q{Simple pure-ruby port of Ozawa's RBTree'}
|
17
17
|
s.description = <<-EOS.strip
|
18
|
-
|
18
|
+
This is a pure-ruby port of Takuma Ozawa's RBTree. (it has an identical
|
19
|
+
interface and uses the identical unit tests from his version 0.3.0,
|
20
|
+
however it is *not* a Red-Black tree.) It's intended for doing lookups and
|
21
|
+
not for doing lots of insertions or deletions. Please see RBTree docs
|
22
|
+
for a sense of how this is supposed to be used.
|
23
|
+
(This one runs the unit tests about 15% slower than Ozawa's C-version,
|
24
|
+
and 80% less lines of code ;)
|
19
25
|
EOS
|
26
|
+
|
20
27
|
# s.rubyforge_project = "webrat"
|
21
28
|
|
22
29
|
require "git"
|
@@ -113,4 +120,90 @@ class Release < Thor
|
|
113
120
|
def gem
|
114
121
|
sh "gem push pkg/#{read_gemspec.file_name}"
|
115
122
|
end
|
116
|
-
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#************ the below added by mark************
|
126
|
+
class Spec < Thor
|
127
|
+
desc "crazy", "try to pull in the latest rbtree test file,\n"+
|
128
|
+
" modify it and save it."
|
129
|
+
def crazy
|
130
|
+
require 'pp'
|
131
|
+
@gem_basename = 'rbtree'
|
132
|
+
@my_version = Gem::Version.new('0.2.9')
|
133
|
+
dep = Gem::Dependency.new @gem_basename, Gem::Requirement.default
|
134
|
+
|
135
|
+
specs = Gem.source_index.search dep
|
136
|
+
local_tuples = specs.map do |spec|
|
137
|
+
[[spec.name, spec.version, spec.original_platform, spec], :local]
|
138
|
+
end
|
139
|
+
local_tuples.extend Crazy
|
140
|
+
puts "local #{@gem_basename} gem(s) currently installed:\n" + local_tuples.print
|
141
|
+
if (t=local_tuples.max_tuple and t[0][1] > @my_version)
|
142
|
+
begin; make_test_file(t); rescue => e; puts e.message; end
|
143
|
+
else
|
144
|
+
puts "your testfile is probably up to date."
|
145
|
+
end
|
146
|
+
|
147
|
+
print "\nShould we check for newer versions of #{@gem_basename} remotely? (y/n):";
|
148
|
+
if ('y'==$stdin.gets.strip)
|
149
|
+
begin
|
150
|
+
puts "attempting to fetch remote info about #{@gem_basename}..."
|
151
|
+
fetcher = Gem::SpecFetcher.fetcher
|
152
|
+
remote_tuples = fetcher.find_matching dep, 'all versions', 'match platform', 'prerelease'
|
153
|
+
puts "done attempting remote"
|
154
|
+
rescue Gem::RemoteFetcher::FetchError => e
|
155
|
+
puts "Failed To Connect? (will try local cache) "+e.message
|
156
|
+
require 'rubygems/source_info_cache'
|
157
|
+
dep.name = '' if dep.name == //
|
158
|
+
specs = Gem::SourceInfoCache.search_with_source dep, false, all
|
159
|
+
remote_tuples = specs.map do |spec, source_uri|
|
160
|
+
[[spec.name, spec.version, spec.original_platform, spec],
|
161
|
+
source_uri]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
remote_tuples.extend Crazy
|
165
|
+
puts "remote #{@gem_basename} gems found:"+remote_tuples.print
|
166
|
+
if (t = remote_tuples.max_tuple and t[0][1] > @my_version)
|
167
|
+
str = t[0][1].to_s
|
168
|
+
puts "The remote version of #{@gem_basename} (#{str}) is more recent than "+
|
169
|
+
"the test file in version control. (#{@my_version}) Consider updating your #{@gem_basename} gem."
|
170
|
+
end
|
171
|
+
end
|
172
|
+
puts 'done.';
|
173
|
+
end
|
174
|
+
def make_test_file tuple
|
175
|
+
dir_basename = tuple[0][0]+'-'+tuple[0][1].to_s
|
176
|
+
filename = File.dirname(__FILE__)+'/test-'+tuple[0][1].to_s+'.rb'
|
177
|
+
if File.exists? filename
|
178
|
+
puts %{\n\nFile already exists: #{File.basename filename}. No need to run script?}
|
179
|
+
return
|
180
|
+
end
|
181
|
+
path = File.expand_path("#{File.dirname(__FILE__)}/../#{dir_basename}/test.rb")
|
182
|
+
raise "sorry, couldn't find test file to copy: #{path}" unless File.exist? path
|
183
|
+
contents = nil
|
184
|
+
File.open(path,'r'){ |fh| contents = fh.read }
|
185
|
+
unless md = %r{\Arequire "\./rbtree"(.+)class MultiRBTreeTest <.+\Z}m.match(contents)
|
186
|
+
raise "sorry, failed to parse file contents."
|
187
|
+
end
|
188
|
+
File.open filename, 'w+' do |fh|
|
189
|
+
msg = %{#Generated #{Time.now.strftime('%Y/%m/%d %I:%M:%S%p')} by #{__FILE__}}
|
190
|
+
fh.write %{require 'rubygems'\nrequire 'hipe-simplebtree'\n\n}+md[1].gsub('RBTree','Hipe::SimpleBTree')
|
191
|
+
end
|
192
|
+
puts %{\n\nGenerated test file. Try running "ruby #{File.basename(filename)}" and keep your fingers crossed!}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
module Crazy
|
197
|
+
def print
|
198
|
+
return '[none]' if count == 0
|
199
|
+
lines = []
|
200
|
+
self.each{|t| lines << %{#{t[0][0]} #{t[0][1]}} }
|
201
|
+
lines * "\n"
|
202
|
+
end
|
203
|
+
def max_tuple
|
204
|
+
self.inject{|left,right| left[0][1] > right[0][1] ? left : right }
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
#arr = Gem.source_index.find_name('gem_basename')
|
209
|
+
#list = Gem::CommandManager.instance['list']
|
data/lib/hipe-simplebtree.rb
CHANGED
@@ -1,37 +1,421 @@
|
|
1
1
|
module Hipe
|
2
|
-
# Experimental *simple* b-tree -- the only purpose of this for now is to
|
3
|
-
#
|
4
|
-
# that is
|
2
|
+
# Experimental *simple* b-tree -- the only purpose of this for now is to
|
3
|
+
# implement a method for "find the lowest key that is greater than the provided key"
|
4
|
+
# (or find the greatest key that is lower than a provided key)
|
5
|
+
# It won't be efficient or useful
|
5
6
|
# for adding/removes lots of nodes, only for providing the above service.
|
6
|
-
# It is presumed that it will be slower than RBTrees (Red-Black tree), but
|
7
|
+
# It is presumed that it will be slower than RBTrees (Red-Black tree), but
|
7
8
|
# faster than scanning all the elements of a sorted array to find this index.
|
8
|
-
class SimpleBTree
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# @
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
|
20
|
-
@
|
10
|
+
class SimpleBTree < Hash
|
11
|
+
|
12
|
+
VERSION = '0.0.1'
|
13
|
+
|
14
|
+
# @see Hash#new
|
15
|
+
def initialize(*args,&block)
|
16
|
+
@locked_stack = 0
|
17
|
+
@insepcting = false
|
18
|
+
@autosort = true
|
19
|
+
super
|
20
|
+
@cmp_proc = nil
|
21
|
+
@sorted_keys = [] # with a new hash it is always empty, right? (we don't have [] literal construtors)
|
22
|
+
@tree = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# @see Hash::[]
|
26
|
+
def self.[](*args)
|
27
|
+
self.new.send(:init_with_brackets, args)
|
21
28
|
end
|
29
|
+
|
30
|
+
attr_accessor :cmp_proc
|
22
31
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
32
|
+
def cmp_proc= proc
|
33
|
+
raise TypeError.new(%{must be proc not "#{proc.class}"}) unless (@cmp_proc = proc).instance_of?(Proc)||(!proc)
|
34
|
+
sort_keys!
|
35
|
+
end
|
36
|
+
|
37
|
+
# the unless method_defined? below are so we can reload this file from irb
|
38
|
+
protected
|
39
|
+
alias_method :hash_set, %s{[]=} unless method_defined? :hash_set
|
40
|
+
alias_method :hash_delete, :delete unless method_defined? :hash_delete
|
41
|
+
alias_method :hash_keys, :keys unless method_defined? :hash_keys
|
42
|
+
alias_method :hash_replace, :replace unless method_defined? :hash_replace
|
43
|
+
alias_method :hash_inspect, :inspect unless method_defined? :hash_inspect
|
44
|
+
|
45
|
+
public
|
46
|
+
|
47
|
+
def clone
|
48
|
+
clone = self.class.new( &default_proc )
|
49
|
+
clone.cmp_proc = self.cmp_proc
|
50
|
+
clone.default = self.default unless clone.default_proc # very important! 1 hr. bug
|
51
|
+
clone.update_with_hash self
|
52
|
+
clone
|
53
|
+
end
|
54
|
+
|
55
|
+
def default *args
|
56
|
+
ret =
|
57
|
+
if 0==args.size then super
|
58
|
+
elsif 2<=args.size then raise ArgumentError.new("expecting 0 or 1 had #{args.size}")
|
59
|
+
elsif default_proc.nil? then super
|
60
|
+
else
|
61
|
+
self.default_proc.call self, args[0]
|
62
|
+
end
|
63
|
+
ret
|
64
|
+
end
|
65
|
+
|
66
|
+
def readjust *args, &proc2
|
67
|
+
size = args.size + (proc2 ? 1 : 0)
|
68
|
+
raise ArgumentError.new("wrong number of arguments - #{size} for 1") if size > 1
|
69
|
+
new_proc = (args.size > 0 && args[0].nil?) ? default_cmp_proc : (proc2 || args[0])
|
70
|
+
my_keys = hash_keys
|
71
|
+
# try the sort before doing any permanant change because it might throw type comparison error
|
72
|
+
my_keys.sort!(&new_proc)
|
73
|
+
@sorted_keys = my_keys
|
74
|
+
@cmp_proc = new_proc
|
75
|
+
end
|
76
|
+
|
77
|
+
def default_cmp_proc
|
78
|
+
return Proc.new{|x,y| x <=> y}
|
79
|
+
end
|
80
|
+
|
81
|
+
def _first_or_last(which)
|
82
|
+
if @sorted_keys.size > 0
|
83
|
+
k = @sorted_keys.send(which)
|
84
|
+
[get_cloned_key(k), self[k]]
|
85
|
+
else
|
86
|
+
default_proc ? default_proc.call(self) : default
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def first; _first_or_last(:first); end
|
91
|
+
|
92
|
+
def last; _first_or_last(:last); end
|
93
|
+
|
94
|
+
def get_cloned_key key
|
95
|
+
@clones ||= {}
|
96
|
+
unless @clones.has_key? key
|
97
|
+
# not sure what the test is trying to accomplish here
|
98
|
+
@clones[key] = key.instance_of?(String) ? key.clone.freeze : key
|
99
|
+
end
|
100
|
+
@clones[key]
|
101
|
+
end
|
102
|
+
|
103
|
+
def == (other)
|
104
|
+
super && @sorted_keys == other.send(:sorted_keys) && @cmp_proc == other.cmp_proc
|
105
|
+
end
|
106
|
+
|
107
|
+
def each &proc
|
108
|
+
return _enumerate(proc){ |k| [k, self[k]] }
|
26
109
|
end
|
27
110
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
111
|
+
def reverse_each &proc
|
112
|
+
return _enumerate(proc,@sorted_keys.reverse){ |k| [k, self[k]] }
|
113
|
+
end
|
114
|
+
|
115
|
+
alias_method :each_pair, :each
|
116
|
+
|
117
|
+
def each_key &proc
|
118
|
+
return _enumerate(proc){ |k| k } if block_given?
|
119
|
+
@sorted_keys.map{|k| [k] }.each
|
120
|
+
end
|
121
|
+
|
122
|
+
def each_value &proc
|
123
|
+
return _enumerate(proc){|k| self[k]} if block_given?
|
124
|
+
@sorted_keys.map{|k| [self[k]] }.each
|
125
|
+
end
|
126
|
+
|
127
|
+
def delete_if
|
128
|
+
return each unless block_given?
|
129
|
+
ks = []
|
130
|
+
_enumerate(Proc.new{|k,v| ks << k if yield(k,v) }){|k| [k,self[k]]}
|
131
|
+
ks.each { |k| hash_delete k }
|
132
|
+
if ks.size > 0
|
133
|
+
sort_keys!
|
134
|
+
self
|
135
|
+
else
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
alias_method %s{reject!}, :delete_if
|
141
|
+
|
142
|
+
def select &proc
|
143
|
+
if block_given?
|
144
|
+
eaches = []
|
145
|
+
@sorted_keys.each { |k| eaches << [k,self[k]] if proc.call(k,self[k]) }
|
146
|
+
Hipe::SimpleBTree[eaches] #* we don't know what to do about default & default proc & sort # note 4
|
147
|
+
else
|
148
|
+
each
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def reject &proc
|
153
|
+
return each unless block_given?
|
154
|
+
guy = select{ |k,v| ! proc.call(k,v) }
|
155
|
+
( guy.size == self.size ) ? nil : guy
|
156
|
+
end
|
157
|
+
|
158
|
+
def pretty_print(q)
|
159
|
+
mybuff = ''
|
160
|
+
if @inspecting
|
161
|
+
mybuff << %["#<#{self.class}: ...>"]
|
162
|
+
else
|
163
|
+
mybuff << %[#<#{self.class}: ]
|
164
|
+
@inspecting = true;
|
165
|
+
els = @sorted_keys.map do |k| # @todo learn more about PP to clean this up
|
166
|
+
PP.pp(k, s='');s.chop!; s << '=>'; PP.pp(self[k],x=''); s<<x.strip!;
|
167
|
+
s
|
168
|
+
end
|
169
|
+
str = els.join(', '); br = " ";
|
170
|
+
if str.length > 79
|
171
|
+
str = els.join(",\n "); br = "\n ";
|
172
|
+
end
|
173
|
+
br = "\n " if str.include? self.class.to_s # total hack to get it to pass the tests
|
174
|
+
PP.pp(default,def_s='')
|
175
|
+
PP.pp(cmp_proc,cmp_s='')
|
176
|
+
mybuff << %({#{str}},#{br}default=#{def_s.chop},#{br}cmp_proc=#{cmp_s.chop}>)
|
177
|
+
@inspecting = false
|
178
|
+
end
|
179
|
+
q.text(mybuff)
|
180
|
+
end
|
181
|
+
|
182
|
+
def inspect
|
183
|
+
if @inspecting
|
184
|
+
%{#<#{self.class.name}: ...>}
|
185
|
+
else
|
186
|
+
@inspecting = true
|
187
|
+
# /#<Hipe::SimpleBTree: (\{.*\}), default=(.*), cmp_proc=(.*)>/
|
188
|
+
ret = %[#<#{self.class.name}: #{hash_inspect}, default=#{default.inspect}, cmp_proc=#{cmp_proc.inspect}>]
|
189
|
+
@inspecting = false
|
190
|
+
ret
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def []= k, value
|
195
|
+
raise TypeError.new("can't modify simplebtree in iteration") if @locked_stack > 0
|
196
|
+
use_key = k
|
197
|
+
if (!has_key?(k))
|
198
|
+
my_keys = @sorted_keys.dup # the unit test requires this
|
199
|
+
my_keys << use_key
|
200
|
+
my_keys.sort!(&@cmp_proc) # we want this to throw type comparison error
|
201
|
+
@sorted_keys = my_keys
|
202
|
+
@tree = nil # we loose a lot of sorted data when we add just one element. # note 6.
|
34
203
|
end
|
204
|
+
super use_key, value
|
35
205
|
end
|
36
|
-
|
37
|
-
|
206
|
+
|
207
|
+
def pop
|
208
|
+
if @sorted_keys.size == 0
|
209
|
+
ret = default_proc ? default_proc.call(self, nil) : default
|
210
|
+
else
|
211
|
+
key = @sorted_keys.pop
|
212
|
+
@tree = nil
|
213
|
+
ret = [key, delete(key)]
|
214
|
+
end
|
215
|
+
return ret
|
216
|
+
end
|
217
|
+
|
218
|
+
def delete key
|
219
|
+
unless has_key? key
|
220
|
+
ret = block_given? ? yield : nil
|
221
|
+
else
|
222
|
+
ret = super
|
223
|
+
sort_keys!
|
224
|
+
end
|
225
|
+
ret
|
226
|
+
end
|
227
|
+
|
228
|
+
def flatten; each.flatten; end
|
229
|
+
|
230
|
+
def clear; super; @sorted_keys.clear; @tree = nil; end
|
231
|
+
|
232
|
+
def update x; super x; sort_keys!; end
|
233
|
+
|
234
|
+
def invert; self.class[super]; end
|
235
|
+
|
236
|
+
def keys; @sorted_keys.dup; end
|
237
|
+
|
238
|
+
def values; @sorted_keys.map{|k|self[k]}; end
|
239
|
+
|
240
|
+
def to_a; each.to_a; end
|
241
|
+
|
242
|
+
def to_s; to_a.to_s; end
|
243
|
+
|
244
|
+
def merge x; #not sure why super way wouldn't work
|
245
|
+
clone = self.class[self]
|
246
|
+
x.each do |x,y|
|
247
|
+
clone.hash_set(x,y)
|
248
|
+
end
|
249
|
+
clone.sort_keys!
|
250
|
+
clone
|
251
|
+
end
|
252
|
+
|
253
|
+
def to_rbtree
|
254
|
+
self # self.class[self]
|
255
|
+
end
|
256
|
+
|
257
|
+
def stats; tree.stats; end
|
258
|
+
|
259
|
+
def lower_bound key; _bound(:lower_bound_index,key) end
|
260
|
+
|
261
|
+
def upper_bound key; _bound(:upper_bound_index,key) end
|
262
|
+
|
263
|
+
# @return an array containing key-value pairs between the result of lower_bound
|
264
|
+
# and upper_bound. If a block is given it calls the block once for each pair.
|
265
|
+
def bound key1, key2=key1
|
266
|
+
#require 'ruby-debug'
|
267
|
+
#debugger
|
268
|
+
return [] unless i1 = tree.lower_bound_index(key1) and i2 = tree.upper_bound_index(key2)
|
269
|
+
if block_given? #note 9
|
270
|
+
@locked_stack += 1
|
271
|
+
(i1..i2).each{ |i| yield @sorted_keys[i], self[@sorted_keys[i]] }
|
272
|
+
@locked_stack -= 1
|
273
|
+
end
|
274
|
+
(i1..i2).map{ |i| [@sorted_keys[i], self[@sorted_keys[i]]] }
|
275
|
+
end
|
276
|
+
|
277
|
+
def replace tree
|
278
|
+
unless tree.instance_of? self.class
|
279
|
+
raise TypeError.new(%{wrong argument type #{tree.class} (expected #{self.class})})
|
280
|
+
end
|
281
|
+
hash_replace tree
|
282
|
+
@cmp_proc = tree.cmp_proc
|
283
|
+
@default = tree.default
|
284
|
+
sort_keys!
|
285
|
+
end
|
286
|
+
|
287
|
+
def dump
|
288
|
+
TypeError.new(%{cannot dump #{self.class} with default proc}) if @default_proc
|
289
|
+
TypeError.new(%{cannot dump #{self.class} with compare proc}) if @cmp_proc
|
290
|
+
Marshal.dump(self)
|
291
|
+
end
|
292
|
+
|
293
|
+
protected
|
294
|
+
|
295
|
+
attr_accessor :sorted_keys
|
296
|
+
|
297
|
+
def _bound which, key
|
298
|
+
index = tree.send which, key
|
299
|
+
index ? [@sorted_keys[index], self[@sorted_keys[index]]] : nil # avoid defaults
|
300
|
+
end
|
301
|
+
|
302
|
+
# there are several ways to construct a SimpleBtree with the [] class method.
|
303
|
+
# These are identical to the variants of the [] method of the Hash class, plus one more
|
304
|
+
# btree = SimpleBtree[{'a'=>'A', 'b'=>'B'}] # from a literal hash
|
305
|
+
# btree = SimpleBtree['a','A','b','B'] # will result in the same as the first example
|
306
|
+
# btree = SimpleBtree[*['a','A','b','B']] # a different way of saying the above
|
307
|
+
# btree = SimpleBtree[hash] # deep-copy a hash object (one level deep)
|
308
|
+
# btree = SimpleBtree[simple_btree] # deep-copy another one (one level deep)
|
309
|
+
def init_with_brackets args
|
310
|
+
|
311
|
+
if args.size!=1
|
312
|
+
raise ArgumentError.new(%{odd number of arguments for #{self}}) unless args.size % 2 == 0
|
313
|
+
use_as_hash = Hash[*args]
|
314
|
+
else
|
315
|
+
arg = args[0]
|
316
|
+
case args[0]
|
317
|
+
when SimpleBTree then use_as_hash = arg
|
318
|
+
when Hash then use_as_hash = arg
|
319
|
+
when Array
|
320
|
+
# allow construction like this: btree = SimpleBTree[['a'],['b','B'],['c']..]
|
321
|
+
if (arg.size > 0 && arg[0].instance_of?(Array))
|
322
|
+
arg.map!{|x| x.size == 1 ? [x[0], nil] : x }
|
323
|
+
end
|
324
|
+
use_as_hash = Hash[*arg.flatten] # arg might be one- or two-dimensional array
|
325
|
+
else
|
326
|
+
raise ArgumentError.new("Don't know how to construct with a single argument of class #{args[0].class}")
|
327
|
+
end # case
|
328
|
+
end # if-else
|
329
|
+
update_with_hash use_as_hash
|
330
|
+
self
|
331
|
+
end
|
332
|
+
|
333
|
+
def _enumerate(their_proc=nil,keys=nil,&my_proc)
|
334
|
+
use_keys = keys ? keys : @sorted_keys
|
335
|
+
return use_keys.map{|k| [k,self[k]]} if their_proc.nil?
|
336
|
+
@locked_stack += 1
|
337
|
+
use_keys.each do |k|
|
338
|
+
their_proc.call(*my_proc.call(k)) # do we decrement the sak
|
339
|
+
end
|
340
|
+
@locked_stack -= 1
|
341
|
+
self
|
342
|
+
end
|
343
|
+
|
344
|
+
def update_with_hash hash
|
345
|
+
hash.each{|k,v| hash_set(k,v) }
|
346
|
+
sort_keys! # this of course could be improved if it's a copy of a btree
|
347
|
+
end
|
348
|
+
|
349
|
+
def sort_keys!
|
350
|
+
my_keys = hash_keys
|
351
|
+
my_keys.sort!(&@cmp_proc) #might throw type comparison error
|
352
|
+
@sorted_keys = my_keys
|
353
|
+
end
|
354
|
+
|
355
|
+
def tree
|
356
|
+
@tree = @sorted_keys.size == 0 ? nil : Tree.new(@sorted_keys, 0, @sorted_keys.size-1,1) if @tree.nil?
|
357
|
+
@tree
|
358
|
+
end
|
359
|
+
|
360
|
+
# just to be incendiary, we don't distinguish among leaf nodes, branch nodes, and root nodes.
|
361
|
+
# @private
|
362
|
+
class Tree
|
363
|
+
def initialize(ary, start_index, end_index, depth=0)
|
364
|
+
@depth = depth
|
365
|
+
width = end_index - start_index + 1
|
366
|
+
value_index = start_index + width / 2
|
367
|
+
value_index -= 1 if depth % 2 == 0 and width % 2 == 0 and width != 1
|
368
|
+
@key = ary[value_index]
|
369
|
+
@index = value_index # careful to keep it synced with key!
|
370
|
+
@left = (value_index == start_index) ? nil : Tree.new(ary, start_index, value_index-1, depth + 1)
|
371
|
+
@right = (value_index == end_index) ? nil : Tree.new(ary, value_index + 1, end_index, depth + 1)
|
372
|
+
end
|
373
|
+
|
374
|
+
def stats
|
375
|
+
heights = [1]
|
376
|
+
mins = []
|
377
|
+
num_nodes = 1
|
378
|
+
['@left','@right'].each do |name|
|
379
|
+
child = instance_variable_get(name)
|
380
|
+
if (child)
|
381
|
+
stats = child.stats
|
382
|
+
heights << stats[:height] + 1
|
383
|
+
mins << stats[:min_height]
|
384
|
+
num_nodes += stats[:num_nodes]
|
385
|
+
end
|
386
|
+
end
|
387
|
+
{
|
388
|
+
:height => heights.max,
|
389
|
+
:min_height => (mins.size==0) ? 1 : (mins.min + 1),
|
390
|
+
:num_nodes => num_nodes
|
391
|
+
}
|
392
|
+
end #stats
|
393
|
+
|
394
|
+
# @retrurn the index of the lowest key that is equal to or greater than the given key
|
395
|
+
# (inside of lower boundary). If there is no such key, returns nil.
|
396
|
+
def lower_bound_index key
|
397
|
+
(@key >= key) ? ((@left && @left.lower_bound_index(key)) || @index) : (@right && @right.lower_bound_index(key))
|
398
|
+
end
|
399
|
+
|
400
|
+
# @return the index of the greatest key that is equal to or lower than the given key
|
401
|
+
# (inside of upper boundary). If there is no such key, returns nil.
|
402
|
+
def upper_bound_index key
|
403
|
+
(@key <= key) ? ((@right && @right.upper_bound_index(key)) || @index) : (@left && @left.upper_bound_index(key))
|
404
|
+
end
|
405
|
+
|
406
|
+
end # class Tree
|
407
|
+
end # class SimpleBTree
|
408
|
+
end # Hipe
|
409
|
+
|
410
|
+
|
411
|
+
# note 1: DONE consider making this descend from Hash
|
412
|
+
# note 2: when to copy over cmp_proc et all? when not to?
|
413
|
+
# note 3: DONE reafactor iterators to all use _enumerate
|
414
|
+
# note 4: (deep copy questions)
|
415
|
+
# note 5: when we are running this from irb we only want to do alias-method once
|
416
|
+
# note 6: i know nothing about efficient ways to re-tree
|
417
|
+
# note 7: considering making a lazy accessor for sorted_keys like tree
|
418
|
+
# note 8: these are points to consider if we ever do MultiSimpleBtree
|
419
|
+
# note 9: there are at least 3 ways we could have done this, with tradeoffs
|
420
|
+
# alternately among code size/duplication, performance when block given, performance
|
421
|
+
# when block not given. The current way is short and fast for when a block is not given.
|