sorted_array_binary 0.0.2 → 0.0.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/README.md +9 -3
- data/lib/sorted_array_binary.rb +14 -118
- data/perf-tests/profile +12 -0
- data/sorted_array_binary.gemspec +3 -2
- data/spec/sorted_array_binary_spec.rb +5 -183
- metadata +19 -2
data/README.md
CHANGED
@@ -31,8 +31,8 @@ array.push 'b', 'a' #=> ['a', 'b']
|
|
31
31
|
array = SortedArrayBinary.new { |a, b| b <=> a }
|
32
32
|
array.push 'a', 'b' #=> ['b', 'a']
|
33
33
|
|
34
|
-
#
|
35
|
-
array.push nil #=>
|
34
|
+
# Take care to only add items that can be compared with <=>.
|
35
|
+
array.push nil, 1 #=> exception is raised
|
36
36
|
```
|
37
37
|
|
38
38
|
## Performance
|
@@ -41,5 +41,11 @@ When #push'ing 1000 random numbers into an array:
|
|
41
41
|
```
|
42
42
|
sorted_array (0.0.5) 1.179088
|
43
43
|
array-sorted (1.1.2) 0.076348
|
44
|
-
sorted_array_binary (0.0.
|
44
|
+
sorted_array_binary (0.0.3) 0.005244
|
45
|
+
```
|
46
|
+
|
47
|
+
## Installation
|
48
|
+
|
49
|
+
```
|
50
|
+
gem install sorted_array_binary
|
45
51
|
```
|
data/lib/sorted_array_binary.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Copyright (C) 2014 by Dmitry Maksyoma <ledestin@gmail.com>
|
2
2
|
|
3
|
+
require 'bsearch'
|
4
|
+
|
3
5
|
# Automatically sorted array (by using binary search). Nils aren't allowed.
|
4
6
|
# Methods that reorder elements are not implemented, as well as #[]= and #fill.
|
5
7
|
#
|
@@ -14,37 +16,6 @@
|
|
14
16
|
# array = SortedArrayBinary.new { |a, b| b <=> a }
|
15
17
|
# array.push 'a', 'b' #=> ['b', 'a']
|
16
18
|
class SortedArrayBinary < Array
|
17
|
-
# {{{2 ComparisonState
|
18
|
-
class ComparisonState #:nodoc:
|
19
|
-
def initialize state
|
20
|
-
raise ArgumentError, "invalid state: #{state.inspect}" \
|
21
|
-
unless state && state >= -1 && state <= 1
|
22
|
-
|
23
|
-
@state = state
|
24
|
-
end
|
25
|
-
|
26
|
-
def equal?
|
27
|
-
@state == 0
|
28
|
-
end
|
29
|
-
|
30
|
-
def greater?
|
31
|
-
@state == 1
|
32
|
-
end
|
33
|
-
|
34
|
-
def less?
|
35
|
-
@state == -1
|
36
|
-
end
|
37
|
-
end
|
38
|
-
# }}}2
|
39
|
-
|
40
|
-
class InvalidSortBlock < RuntimeError #:nodoc:
|
41
|
-
end
|
42
|
-
|
43
|
-
def self._check_for_nil *objs #:nodoc:
|
44
|
-
raise ArgumentError, "nils aren't allowed into sorted array" \
|
45
|
-
if objs.include?(nil)
|
46
|
-
end
|
47
|
-
|
48
19
|
alias :old_insert :insert
|
49
20
|
private :old_insert
|
50
21
|
alias :old_sort! :sort!
|
@@ -63,7 +34,6 @@ class SortedArrayBinary < Array
|
|
63
34
|
if args.size == 1
|
64
35
|
# Passed initial array.
|
65
36
|
if args.first.respond_to? :each
|
66
|
-
self.class._check_for_nil *args.first
|
67
37
|
super *args
|
68
38
|
old_sort!
|
69
39
|
return
|
@@ -72,14 +42,9 @@ class SortedArrayBinary < Array
|
|
72
42
|
# Passed size and block.
|
73
43
|
if block_given?
|
74
44
|
super *args, &b
|
75
|
-
self.class._check_for_nil *self
|
76
45
|
old_sort!
|
77
46
|
return
|
78
47
|
end
|
79
|
-
|
80
|
-
# Passed size, but not obj, which means fill with nils.
|
81
|
-
raise ArgumentError, "can't fill array with nils" \
|
82
|
-
if args.first.is_a? Numeric
|
83
48
|
end
|
84
49
|
|
85
50
|
super
|
@@ -94,123 +59,54 @@ class SortedArrayBinary < Array
|
|
94
59
|
raise NotImplementedError
|
95
60
|
end
|
96
61
|
|
97
|
-
[:[]=, :fill, :insert, :reverse!, :rotate!, :shuffle
|
62
|
+
[:[]=, :fill, :insert, :reverse!, :rotate!, :shuffle!].
|
98
63
|
each { |m|
|
99
64
|
alias_method m, :_not_implemented
|
100
65
|
}
|
101
66
|
|
102
|
-
|
103
|
-
# * Disallow nils in the resulting array.
|
104
|
-
# * The resulting array is sorted.
|
105
|
-
def collect! &b
|
67
|
+
def collect! &b #:nodoc:
|
106
68
|
replace(collect &b)
|
107
69
|
end
|
108
70
|
alias :map! :collect!
|
109
71
|
|
110
|
-
|
111
|
-
# * Disallow nils in the passed array.
|
112
|
-
# * The resulting array is sorted.
|
113
|
-
def concat other_ary
|
72
|
+
def concat other_ary #:nodoc:
|
114
73
|
_add *other_ary
|
115
74
|
end
|
116
75
|
|
117
|
-
|
118
|
-
# * Disallow nils in the resulting array.
|
119
|
-
# * The resulting array is sorted.
|
120
|
-
def flatten! *args
|
76
|
+
def flatten! *args #:nodoc:
|
121
77
|
replace(flatten *args)
|
122
78
|
end
|
123
79
|
|
124
|
-
# Add objects to array, automatically placing them according to sort order
|
125
|
-
#
|
80
|
+
# Add objects to array, automatically placing them according to sort order
|
81
|
+
# (via <=> by default).
|
126
82
|
def push *objs
|
127
83
|
_add *objs
|
128
84
|
end
|
129
85
|
alias :<< :push
|
86
|
+
alias :unshift :push
|
130
87
|
|
131
|
-
|
132
|
-
# * Disallow nils in @other_ary.
|
133
|
-
# * The resulting array is sorted.
|
134
|
-
def replace other_ary
|
135
|
-
self.class._check_for_nil *other_ary
|
88
|
+
def replace other_ary #:nodoc:
|
136
89
|
super
|
137
90
|
old_sort! &@sort_block
|
138
91
|
self
|
139
92
|
end
|
140
93
|
|
94
|
+
def sort! #:nodoc:
|
95
|
+
end
|
96
|
+
|
141
97
|
#private
|
142
98
|
# Name the following methods starting with underscore so as not to pollute
|
143
99
|
# Array namespace. They are considered private, but for testing purposes are
|
144
100
|
# left public.
|
145
101
|
|
146
102
|
def _add *objs #:nodoc:
|
147
|
-
self.class._check_for_nil *objs
|
148
103
|
objs.each { |obj|
|
149
104
|
old_insert _find_insert_position(obj), obj
|
150
105
|
}
|
151
106
|
self
|
152
107
|
end
|
153
108
|
|
154
|
-
def _check_can_calc_boundary? #:nodoc:
|
155
|
-
raise "can't calc boundary on empty array" if empty?
|
156
|
-
end
|
157
|
-
|
158
|
-
def _compare a, b #:nodoc:
|
159
|
-
ComparisonState.new(ret = @sort_block.call(a, b))
|
160
|
-
rescue ArgumentError
|
161
|
-
raise InvalidSortBlock,
|
162
|
-
"sort block returned invalid value: #{ret.inspect}"
|
163
|
-
end
|
164
|
-
|
165
109
|
def _find_insert_position element_to_place #:nodoc:
|
166
|
-
|
167
|
-
|
168
|
-
start_idx, end_idx = 0, size - 1
|
169
|
-
loop {
|
170
|
-
middle_idx = _middle_element_index(start_idx, end_idx)
|
171
|
-
middle_el = self[middle_idx]
|
172
|
-
after_middle_idx = middle_idx + 1
|
173
|
-
|
174
|
-
comparison_state = _compare(element_to_place, middle_el)
|
175
|
-
|
176
|
-
# 1. Equals to the middle element. Insert after el.
|
177
|
-
return after_middle_idx if comparison_state.equal?
|
178
|
-
|
179
|
-
# 2. Less than the middle element.
|
180
|
-
if comparison_state.less?
|
181
|
-
# There's nothing to the left. So insert it as the first element.
|
182
|
-
return 0 if _left_boundary? middle_idx
|
183
|
-
|
184
|
-
end_idx = middle_idx - 1
|
185
|
-
next
|
186
|
-
end
|
187
|
-
|
188
|
-
# 3. Greater than the middle element.
|
189
|
-
#
|
190
|
-
# Right boundary? Put element_to_place after the last (middle) element.
|
191
|
-
return after_middle_idx if _right_boundary? middle_idx
|
192
|
-
|
193
|
-
# Less than after middle element? Put it right before it!
|
194
|
-
after_middle_el = self[after_middle_idx]
|
195
|
-
ret = _compare(element_to_place, after_middle_el)
|
196
|
-
return after_middle_idx if ret.equal? || ret.less?
|
197
|
-
|
198
|
-
# Proceeed to divide the right part.
|
199
|
-
start_idx = after_middle_idx
|
200
|
-
}
|
201
|
-
end
|
202
|
-
|
203
|
-
def _left_boundary? idx #:nodoc:
|
204
|
-
_check_can_calc_boundary?
|
205
|
-
idx == 0
|
206
|
-
end
|
207
|
-
|
208
|
-
def _middle_element_index start, ending #:nodoc:
|
209
|
-
start + (ending - start)/2
|
210
|
-
end
|
211
|
-
|
212
|
-
def _right_boundary? idx #:nodoc:
|
213
|
-
_check_can_calc_boundary?
|
214
|
-
idx == size - 1
|
110
|
+
bsearch_upper_boundary { |el| @sort_block.call el, element_to_place }
|
215
111
|
end
|
216
112
|
end
|
data/perf-tests/profile
ADDED
data/sorted_array_binary.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'sorted_array_binary'
|
3
|
-
s.version = '0.0.
|
4
|
-
s.date = '2014-01-
|
3
|
+
s.version = '0.0.3'
|
4
|
+
s.date = '2014-01-31'
|
5
5
|
s.summary = 'Sorted array'
|
6
6
|
s.description = 'Sorted array using binary search'
|
7
7
|
s.authors = ['Dmitry Maksyoma']
|
@@ -13,4 +13,5 @@ Gem::Specification.new do |s|
|
|
13
13
|
|
14
14
|
s.add_development_dependency 'rspec', '>= 2.13'
|
15
15
|
s.add_development_dependency 'hitimes', '~> 1.2'
|
16
|
+
s.add_runtime_dependency 'bsearch', '~> 1.5'
|
16
17
|
end
|
@@ -12,21 +12,6 @@ describe SortedArrayBinary do
|
|
12
12
|
@ar = SortedArrayBinary.new
|
13
13
|
end
|
14
14
|
|
15
|
-
# {{{2 self._check_for_nil
|
16
|
-
context 'self._check_for_nil' do
|
17
|
-
it "raises exception if there's nil in passed objs" do
|
18
|
-
expect {
|
19
|
-
SortedArrayBinary._check_for_nil 'a', nil
|
20
|
-
}.to raise_error ArgumentError
|
21
|
-
end
|
22
|
-
|
23
|
-
it "doesn't raise exception if there's no nil in passed objs" do
|
24
|
-
expect {
|
25
|
-
SortedArrayBinary._check_for_nil 'a', 'b'
|
26
|
-
}.not_to raise_error
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
15
|
# {{{2 self.new
|
31
16
|
context 'self.new' do
|
32
17
|
it 'if passed size and obj, fills it' do
|
@@ -34,23 +19,13 @@ describe SortedArrayBinary do
|
|
34
19
|
@ar.should == ['a']*5
|
35
20
|
end
|
36
21
|
|
37
|
-
it 'if passed just size, raises exception' do
|
38
|
-
expect { SortedArrayBinary.new 5 }.to raise_error ArgumentError
|
39
|
-
end
|
40
|
-
|
41
22
|
it 'if passed single non-numeric argument, calls Array#new' do
|
42
23
|
expect { SortedArrayBinary.new 'a' }.to raise_error TypeError
|
43
24
|
end
|
44
25
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
@ar.should == ['a', 'b']
|
49
|
-
end
|
50
|
-
|
51
|
-
it 'raises exception if nil found in passed array' do
|
52
|
-
expect { SortedArrayBinary.new [nil] }.to raise_error ArgumentError
|
53
|
-
end
|
26
|
+
it 'if passed array, it sorts it' do
|
27
|
+
@ar = SortedArrayBinary.new ['b', 'a']
|
28
|
+
@ar.should == ['a', 'b']
|
54
29
|
end
|
55
30
|
|
56
31
|
it 'if passed size and block, fills it and sorts it' do
|
@@ -75,7 +50,7 @@ describe SortedArrayBinary do
|
|
75
50
|
expect { @ar.fill nil }.to raise_error NotImplementedError
|
76
51
|
end
|
77
52
|
|
78
|
-
[:insert, :reverse!, :rotate!, :shuffle
|
53
|
+
[:insert, :reverse!, :rotate!, :shuffle!].
|
79
54
|
each { |m|
|
80
55
|
it "##{m}" do
|
81
56
|
expect { @ar.send m }.to raise_error NotImplementedError
|
@@ -104,12 +79,6 @@ describe SortedArrayBinary do
|
|
104
79
|
end
|
105
80
|
end
|
106
81
|
}
|
107
|
-
|
108
|
-
it 'raises exception if one of resulting elements is nil' do
|
109
|
-
@ar.push 'a'
|
110
|
-
expect { @ar.send(method) { nil } }.to raise_error ArgumentError
|
111
|
-
@ar.should == ['a']
|
112
|
-
end
|
113
82
|
end
|
114
83
|
}
|
115
84
|
|
@@ -143,16 +112,10 @@ describe SortedArrayBinary do
|
|
143
112
|
end
|
144
113
|
end
|
145
114
|
}
|
146
|
-
|
147
|
-
it 'raises exception if resulting array contains nil' do
|
148
|
-
@ar.push [nil, 1]
|
149
|
-
expect { @ar.flatten! }.to raise_error ArgumentError
|
150
|
-
@ar.should == [[nil, 1]]
|
151
|
-
end
|
152
115
|
end
|
153
116
|
|
154
117
|
# {{{2 #push
|
155
|
-
[:<<, :push].each { |method|
|
118
|
+
[:<<, :push, :unshift].each { |method|
|
156
119
|
context "##{method}" do
|
157
120
|
it 'adds an element to array' do
|
158
121
|
@ar.send method, 'a'
|
@@ -177,10 +140,6 @@ describe SortedArrayBinary do
|
|
177
140
|
@ar.push 'a', 'b'
|
178
141
|
@ar.should == ['d', 'c', 'b', 'a']
|
179
142
|
end
|
180
|
-
|
181
|
-
it 'raises exception if nil is passed' do
|
182
|
-
expect { @ar.send method, nil }.to raise_error ArgumentError
|
183
|
-
end
|
184
143
|
end
|
185
144
|
}
|
186
145
|
|
@@ -198,52 +157,6 @@ describe SortedArrayBinary do
|
|
198
157
|
end
|
199
158
|
end
|
200
159
|
}
|
201
|
-
|
202
|
-
it "doesn't allow nils" do
|
203
|
-
expect { @ar.replace [nil] }.to raise_error ArgumentError
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
# {{{2 #_compare
|
208
|
-
context '#_compare' do
|
209
|
-
context 'sort block not given' do
|
210
|
-
it 'returns :equal if arguments are equal' do
|
211
|
-
@ar._compare(1, 1).equal?.should be_true
|
212
|
-
end
|
213
|
-
|
214
|
-
it 'returns :less if arg1 < arg2' do
|
215
|
-
@ar._compare(1, 2).less?.should be_true
|
216
|
-
end
|
217
|
-
|
218
|
-
it 'returns :greater if arg1 > arg2' do
|
219
|
-
@ar._compare(2, 1).greater?.should be_true
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
context 'sort block given' do
|
224
|
-
before :each do
|
225
|
-
@ar = SortedArrayBinary.new { |a, b| b <=> a }
|
226
|
-
end
|
227
|
-
|
228
|
-
it 'returns :equal if arguments are equal' do
|
229
|
-
@ar._compare(1, 1).equal?.should be_true
|
230
|
-
end
|
231
|
-
|
232
|
-
it 'returns :less if arg1 < arg2' do
|
233
|
-
@ar._compare(2, 1).less?.should be_true
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'returns :greater if arg1 > arg2' do
|
237
|
-
@ar._compare(1, 2).greater?.should be_true
|
238
|
-
end
|
239
|
-
|
240
|
-
it 'raises exception if block returns value outside of -1, 0, 1' do
|
241
|
-
@ar = SortedArrayBinary.new { 'a' }
|
242
|
-
expect {
|
243
|
-
@ar.push 1, 2
|
244
|
-
}.to raise_error SortedArrayBinary::InvalidSortBlock
|
245
|
-
end
|
246
|
-
end
|
247
160
|
end
|
248
161
|
|
249
162
|
# {{{2 #_find_insert_position
|
@@ -293,96 +206,5 @@ describe SortedArrayBinary do
|
|
293
206
|
end
|
294
207
|
end
|
295
208
|
end
|
296
|
-
|
297
|
-
# {{{2 #_middle_element_index
|
298
|
-
context '_middle_element_index' do
|
299
|
-
it "returns first element index if there's one element" do
|
300
|
-
@ar._middle_element_index(0, 0).should == 0
|
301
|
-
@ar._middle_element_index(1, 1).should == 1
|
302
|
-
end
|
303
|
-
|
304
|
-
it 'returns 0 if there are 2 elements in the array' do
|
305
|
-
@ar._middle_element_index(0, 1).should == 0
|
306
|
-
end
|
307
|
-
|
308
|
-
it 'returns 1 if there are 3 elements in the array' do
|
309
|
-
@ar._middle_element_index(0, 2).should == 1
|
310
|
-
end
|
311
|
-
|
312
|
-
it 'returns 1 if there are 4 elements in the array' do
|
313
|
-
@ar._middle_element_index(0, 3).should == 1
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
# {{{2 #_left_boundary, #_right_boundary
|
318
|
-
context '#_left_boundary, #_right_boundary' do
|
319
|
-
context 'left' do
|
320
|
-
it "returns true if it's left boundary?" do
|
321
|
-
@ar.push 'a'
|
322
|
-
@ar._left_boundary?(0).should == true
|
323
|
-
end
|
324
|
-
|
325
|
-
it "returns false if it's not left boundary?" do
|
326
|
-
@ar.push 'a'
|
327
|
-
@ar._left_boundary?(1).should == false
|
328
|
-
end
|
329
|
-
|
330
|
-
it 'raises exception if array is empty' do
|
331
|
-
expect {
|
332
|
-
@ar._left_boundary?(0)
|
333
|
-
}.to raise_error
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
context 'right' do
|
338
|
-
it "returns true if it's right boundary?" do
|
339
|
-
@ar.push 'a', 'b'
|
340
|
-
@ar._right_boundary?(1).should == true
|
341
|
-
end
|
342
|
-
|
343
|
-
it "returns false if it's not right boundary?" do
|
344
|
-
@ar.push 'a', 'b'
|
345
|
-
@ar._right_boundary?(0).should == false
|
346
|
-
end
|
347
|
-
|
348
|
-
it 'raises exception if array is empty' do
|
349
|
-
expect {
|
350
|
-
@ar._right_boundary?(0)
|
351
|
-
}.to raise_error
|
352
|
-
end
|
353
|
-
end
|
354
|
-
end
|
355
209
|
# }}}2
|
356
210
|
end
|
357
|
-
|
358
|
-
# {{{1 ComparisonState
|
359
|
-
describe SortedArrayBinary::ComparisonState do
|
360
|
-
[nil, -2, 2].each { |state|
|
361
|
-
it "allows only valid state in" do
|
362
|
-
expect {
|
363
|
-
SortedArrayBinary::ComparisonState.new state
|
364
|
-
}.to raise_error ArgumentError
|
365
|
-
end
|
366
|
-
}
|
367
|
-
|
368
|
-
it 'is equal? if state is 0' do
|
369
|
-
state = SortedArrayBinary::ComparisonState.new 0
|
370
|
-
state.equal?.should be_true
|
371
|
-
state.less?.should be_false
|
372
|
-
state.greater?.should be_false
|
373
|
-
end
|
374
|
-
|
375
|
-
it 'is greater? if state is 1' do
|
376
|
-
state = SortedArrayBinary::ComparisonState.new 1
|
377
|
-
state.greater?.should be_true
|
378
|
-
state.equal?.should be_false
|
379
|
-
state.less?.should be_false
|
380
|
-
end
|
381
|
-
|
382
|
-
it 'is less? if state is -1' do
|
383
|
-
state = SortedArrayBinary::ComparisonState.new -1
|
384
|
-
state.less?.should be_true
|
385
|
-
state.equal?.should be_false
|
386
|
-
state.greater?.should be_false
|
387
|
-
end
|
388
|
-
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sorted_array_binary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-01-
|
12
|
+
date: 2014-01-31 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '1.2'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bsearch
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.5'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.5'
|
46
62
|
description: Sorted array using binary search
|
47
63
|
email: ledestin@gmail.com
|
48
64
|
executables: []
|
@@ -61,6 +77,7 @@ files:
|
|
61
77
|
- perf-tests/common.rb
|
62
78
|
- perf-tests/perf-vs-sort
|
63
79
|
- perf-tests/perf-vs-sorted_array
|
80
|
+
- perf-tests/profile
|
64
81
|
- sorted_array_binary.gemspec
|
65
82
|
- spec/sorted_array_binary_spec.rb
|
66
83
|
- spec/spec_helper.rb
|