bin_search 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/AUTHORS +1 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.md +81 -0
- data/Rakefile +34 -0
- data/bin_search.gemspec +36 -0
- data/lib/bin_search.rb +24 -0
- data/lib/bin_search/bin_search.rb +289 -0
- data/lib/bin_search/version.rb +3 -0
- data/spec/lib/bin_search/bin_search_spec.rb +151 -0
- data/spec/spec_helper.rb +2 -0
- metadata +118 -0
data/.gitignore
ADDED
data/AUTHORS
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Stefan Plantikow <stefanp@moviepilot.com>
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Stefan Plantikow
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# bin_search
|
2
|
+
|
3
|
+
This gem implements binary search for arrays and array-like data
|
4
|
+
structures. Binary search is a very fast method for looking up elements in pre-sorted arrays that is missing from the Ruby standard library. For large
|
5
|
+
enough arrays, it will always be more efficient than using regular `assoc` and `index` calls.
|
6
|
+
|
7
|
+
## API
|
8
|
+
|
9
|
+
bin_search adds six methods to Ruby's Array class:
|
10
|
+
|
11
|
+
* `bin_index` - find and return index of given element using `<=>` or the provided comparator block to compare elements
|
12
|
+
* `bin_search` - find and return given element using `<=>` or the provided comparator block to compare elements
|
13
|
+
* `bin_assoc` - find given element and return an assoc pair [index, elem] using `<=>` or the provided comparator block to compare elements
|
14
|
+
* `bin_index_by` - same as `bin_index` but compares elements using `<=>` after mapping them with the provided block
|
15
|
+
* `bin_search_by` - same as `bin_search` but compares elements using `<=>` after mapping them with the provided block
|
16
|
+
* `bin_assoc_by` - same as `bin_assoc` but compares elements using `<=>` after mapping them with the provided block
|
17
|
+
|
18
|
+
Each of these methods needs to be called with the searched element, and a mode as second argument. Available modes are:
|
19
|
+
|
20
|
+
* `:asc` - array has been sorted in ascending order, find first element
|
21
|
+
that is greater than or equal to the given element (default mode)
|
22
|
+
* `:desc` - array has been sorted descending ascending order, find first element that is less than or equal to the given element
|
23
|
+
* `:asc_eq` - same as `:asc` but only retuns the found element if it is equal
|
24
|
+
* `:desc_eq` - same as `:desc` but only retuns the found element if it is equal
|
25
|
+
|
26
|
+
If the sorting requirements of a mode are not met by the used array, the behavior of the bin_search methods is unspecified (i.e. arbitrary weird things may happpen).
|
27
|
+
|
28
|
+
Example
|
29
|
+
|
30
|
+
arr = [1, 2, 2, 3, 8, 9]
|
31
|
+
arr.bin_index(0, :asc) => 0
|
32
|
+
arr.bin_index(1, :asc) => 0
|
33
|
+
arr.bin_index(2, :asc) => 1
|
34
|
+
arr.bin_index(2, :asc_eq) => 1
|
35
|
+
arr.bin_index(2.5, :asc_eq) => -1
|
36
|
+
arr.bin_index(9, :asc) => 5
|
37
|
+
arr.bin_index(10, :asc) => -1
|
38
|
+
|
39
|
+
## Use as Mixin
|
40
|
+
|
41
|
+
class MyArrayClass
|
42
|
+
include ::BinSearch::Methods
|
43
|
+
|
44
|
+
def sort!
|
45
|
+
# ...
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
arr = MyArrayClass.new
|
50
|
+
arr.sort!
|
51
|
+
arr.bin_search(some_elem, :asc)
|
52
|
+
|
53
|
+
## Implementation Notes
|
54
|
+
|
55
|
+
bin_search is a pure ruby implementation of binary search.
|
56
|
+
|
57
|
+
For arrays with less than `1 << ::BinSearch::LIN_BITS` elements, all methods
|
58
|
+
switch to linear search as that is slightly more efficient on moden CPUs
|
59
|
+
(due to processor caching and branch prediction effects).
|
60
|
+
|
61
|
+
## Requirements
|
62
|
+
|
63
|
+
Ruby 1.9
|
64
|
+
|
65
|
+
## Installation
|
66
|
+
|
67
|
+
gem install bin_search
|
68
|
+
|
69
|
+
## Online Docs
|
70
|
+
|
71
|
+
Docs for the latest released gems are to be found in:
|
72
|
+
|
73
|
+
http://rubydoc.info/gems/bin_search
|
74
|
+
|
75
|
+
## Development
|
76
|
+
|
77
|
+
Development happens on the devel branch, cf. boggle/bin_search/tree/devel, too.
|
78
|
+
|
79
|
+
## Status
|
80
|
+
|
81
|
+
Fairly fresh but tested.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
require 'yard'
|
7
|
+
require 'yard/rake/yardoc_task'
|
8
|
+
|
9
|
+
desc 'Run all rspecs'
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
11
|
+
spec.fail_on_error = true
|
12
|
+
spec.verbose = false
|
13
|
+
# spec.rspec_opts = ['--backtrace']
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Run yardoc over project sources'
|
17
|
+
YARD::Rake::YardocTask.new(:ydoc) do |t|
|
18
|
+
t.options = ['--verbose']
|
19
|
+
t.files = ['lib/**/*.rb', '-', 'README.md', 'AUTHORS', 'LICENSE.txt']
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Run irb in project environment'
|
23
|
+
task :console do
|
24
|
+
require 'irb'
|
25
|
+
ARGV.clear
|
26
|
+
IRB.conf[:USE_READLINE] = false if ENV['JRUBY_OPTS'] =~ /--ng/
|
27
|
+
IRB.start
|
28
|
+
end
|
29
|
+
|
30
|
+
task :doc => :ydoc
|
31
|
+
task :docs => :ydoc
|
32
|
+
task :test => :spec
|
33
|
+
task :tests => :spec
|
34
|
+
task :irb => :console
|
data/bin_search.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'bin_search/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'bin_search'
|
7
|
+
s.version = ::BinSearch::VERSION
|
8
|
+
s.summary = 'Binary search in sorted arrays'
|
9
|
+
s.description = 'Pure ruby implementation of binary search for Ruby arrays and similiar data ' +
|
10
|
+
'structures. Supports ascending and descending sort order, searching ' +
|
11
|
+
'for exact and nearest matches, and has a versatile API. Uses linear search ' +
|
12
|
+
'for small arrays to make use of the internal cache of moden CPUs.'
|
13
|
+
s.author = 'Stefan Plantikow'
|
14
|
+
s.email = 'stefan.plantikow@googlemail.com'
|
15
|
+
s.homepage = 'https://github.com/boggle/bin_search'
|
16
|
+
s.rubyforge_project = 'bin_search'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
s.bindir = 'script'
|
24
|
+
s.executables = `git ls-files -- script/*`.split("\n").map{ |f| File.basename(f) }
|
25
|
+
s.licenses = ['PUBLIC DOMAIN WITHOUT ANY WARRANTY']
|
26
|
+
|
27
|
+
s.add_development_dependency 'rspec'
|
28
|
+
s.add_development_dependency 'simplecov'
|
29
|
+
s.add_development_dependency 'rake'
|
30
|
+
s.add_development_dependency 'yard'
|
31
|
+
|
32
|
+
case RUBY_ENGINE.to_sym
|
33
|
+
when :jruby then s.add_development_dependency 'maruku'
|
34
|
+
else s.add_development_dependency 'redcarpet'
|
35
|
+
end
|
36
|
+
end
|
data/lib/bin_search.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'bin_search/version'
|
4
|
+
|
5
|
+
module BinSearch
|
6
|
+
|
7
|
+
def self.files
|
8
|
+
f = []
|
9
|
+
f << 'bin_search/bin_search'
|
10
|
+
f
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load_relative(f)
|
14
|
+
path = "#{File.join(File.dirname(caller[0]), f)}.rb"
|
15
|
+
load path
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.reload!
|
19
|
+
files.each { |f| load_relative f }
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
BinSearch.files.each { |f| require f }
|
@@ -0,0 +1,289 @@
|
|
1
|
+
module BinSearch
|
2
|
+
|
3
|
+
# BinSearch switches to linear search if the numer of elements to sorted
|
4
|
+
# is less than 1 << LIN_BITS (i.e. 2^LIN_BITS - 1)
|
5
|
+
LIN_BITS = 6
|
6
|
+
|
7
|
+
MODES = [ :asc, :desc, :asc_geq, :desc_leq, :asc_eq, :desc_eq ]
|
8
|
+
MODE_IS_ASC = [ :asc, :asc_eq, :asc_geq ]
|
9
|
+
MODE_IS_DESC = [ :desc, :desc_eq, :desc_leq ]
|
10
|
+
MODE_CHECK_EQ = [ :asc_eq, :desc_eq ]
|
11
|
+
|
12
|
+
module Methods
|
13
|
+
|
14
|
+
# Binary search for the first elem in this array matching according to mode in the range
|
15
|
+
# (low..high-1)
|
16
|
+
#
|
17
|
+
# By default, <=> is used to compare elements. Alternatively, a comparator
|
18
|
+
# may be specified as block parameter
|
19
|
+
#
|
20
|
+
# The supported modes are
|
21
|
+
# * :asc - array is expected to be sorted in ascending order, first geq elem is matched
|
22
|
+
# * :asc_eq - array expected to be sorted in ascending order, first eq elem is matched
|
23
|
+
# * :desc - array is expected to be sorted in descending order, first leq elem is matched
|
24
|
+
# * :desc_eq - array is expected to be sorted in descending order, first eq elem is matched
|
25
|
+
#
|
26
|
+
# @param [Object] elem elem to search for
|
27
|
+
# @param [Fixnum] low lower bound (inclusive)
|
28
|
+
# @param [Fixnum] high upper bound (inclusive, -1 for last element)
|
29
|
+
# @param [:asc, :desc, :asc_eq, :desc_eq] mode matching mode
|
30
|
+
# @return [Fixnum] index of first matching elem in self, or -1 if not found
|
31
|
+
#
|
32
|
+
def bin_index(elem, mode, low = 0, high = -1)
|
33
|
+
dir = if ::BinSearch::MODE_IS_ASC.include?(mode) then +1 else -1 end
|
34
|
+
check_eq = ::BinSearch::MODE_CHECK_EQ.include?(mode)
|
35
|
+
high = size - 1 if high < 0
|
36
|
+
if block_given?
|
37
|
+
then _bin_index(elem, low, high, dir, check_eq) { |b| yield elem, b }
|
38
|
+
else _bin_index(elem, low, high, dir, check_eq) { |b| elem <=> b } end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Binary search for the first elem in this array matching according to mode in the range
|
42
|
+
# (low..high-1)
|
43
|
+
#
|
44
|
+
# Elements are compared using <=> after mapping them using the provided block
|
45
|
+
#
|
46
|
+
# The supported modes are
|
47
|
+
# * :asc - array is expected to be sorted in ascending order, first geq elem is matched
|
48
|
+
# * :asc_eq - array expected to be sorted in ascending order, first eq elem is matched
|
49
|
+
# * :desc - array is expected to be sorted in descending order, first leq elem is matched
|
50
|
+
# * :desc_eq - array is expected to be sorted in descending order, first eq elem is matched
|
51
|
+
#
|
52
|
+
# @param [Object] elem elem to search for
|
53
|
+
# @param [Fixnum] low lower bound (inclusive)
|
54
|
+
# @param [Fixnum] high upper bound (inclusive, -1 for last element)
|
55
|
+
# @param [:asc, :desc, :asc_eq, :desc_eq] mode matching mode
|
56
|
+
# @return [Fixnum] index of first matching elem in self, or -1 if not found
|
57
|
+
#
|
58
|
+
def bin_index_by(elem, mode, low = 0, high = -1)
|
59
|
+
dir = if ::BinSearch::MODE_IS_ASC.include?(mode) then +1 else -1 end
|
60
|
+
check_eq = ::BinSearch::MODE_CHECK_EQ.include?(mode)
|
61
|
+
high = size - 1 if high < 0
|
62
|
+
e = yield elem
|
63
|
+
_bin_index(elem, low, high, dir, check_eq) { |b| e <=> (yield b) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Binary search for the first elem in this array matching according to mode in the range
|
67
|
+
# (low..high-1)
|
68
|
+
#
|
69
|
+
# By default, <=> is used to compare elements. Alternatively, a comparator
|
70
|
+
# may be specified as block parameter
|
71
|
+
#
|
72
|
+
# The supported modes are
|
73
|
+
# * :asc - array is expected to be sorted in ascending order, first geq elem is matched
|
74
|
+
# * :asc_eq - array expected to be sorted in ascending order, first eq elem is matched
|
75
|
+
# * :desc - array is expected to be sorted in descending order, first leq elem is matched
|
76
|
+
# * :desc_eq - array is expected to be sorted in descending order, first eq elem is matched
|
77
|
+
#
|
78
|
+
# @param [Object] elem elem to search for
|
79
|
+
# @param [Fixnum] low lower bound (inclusive)
|
80
|
+
# @param [Fixnum] high upper bound (inclusive, nil for last element)
|
81
|
+
# @param [:asc, :desc, :asc_eq, :desc_eq] mode matching mode
|
82
|
+
# @return [Object] first matching elem in self, or nil if not found
|
83
|
+
#
|
84
|
+
def bin_search(elem, mode, low = 0, high = -1)
|
85
|
+
dir = if ::BinSearch::MODE_IS_ASC.include?(mode) then +1 else -1 end
|
86
|
+
check_eq = ::BinSearch::MODE_CHECK_EQ.include?(mode)
|
87
|
+
high = size - 1 if high < 0
|
88
|
+
if block_given?
|
89
|
+
then _bin_search(elem, low, high, dir, check_eq) { |b| yield elem, b }
|
90
|
+
else _bin_search(elem, low, high, dir, check_eq) { |b| elem <=> b } end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Binary search for the first elem in this array matching according to mode in the range
|
94
|
+
# (low..high-1)
|
95
|
+
#
|
96
|
+
# Elements are compared using <=> after mapping them using the provided block
|
97
|
+
#
|
98
|
+
# The supported modes are
|
99
|
+
# * :asc - array is expected to be sorted in ascending order, first geq elem is matched
|
100
|
+
# * :asc_eq - array expected to be sorted in ascending order, first eq elem is matched
|
101
|
+
# * :desc - array is expected to be sorted in descending order, first leq elem is matched
|
102
|
+
# * :desc_eq - array is expected to be sorted in descending order, first eq elem is matched
|
103
|
+
#
|
104
|
+
# @param [Object] elem elem to search for
|
105
|
+
# @param [Fixnum] low lower bound (inclusive)
|
106
|
+
# @param [Fixnum] high upper bound (inclusive, nil for last element)
|
107
|
+
# @param [:asc, :desc, :asc_eq, :desc_eq] mode matching mode
|
108
|
+
# @return [Object] first matching elem in self, or nil if not found
|
109
|
+
#
|
110
|
+
def bin_search_by(elem, mode, low = 0, high = -1)
|
111
|
+
dir = if ::BinSearch::MODE_IS_ASC.include?(mode) then +1 else -1 end
|
112
|
+
check_eq = ::BinSearch::MODE_CHECK_EQ.include?(mode)
|
113
|
+
high = size - 1 if high < 0
|
114
|
+
e = yield elem
|
115
|
+
_bin_search(elem, low, high, dir, check_eq) { |b| e <=> (yield b) }
|
116
|
+
end
|
117
|
+
|
118
|
+
# Binary search for the first elem in this array matching according to mode in the range
|
119
|
+
# (low..high-1)
|
120
|
+
#
|
121
|
+
# By default, <=> is used to compare elements. Alternatively, a comparator
|
122
|
+
# may be specified as block parameter
|
123
|
+
#
|
124
|
+
# The supported modes are
|
125
|
+
# * :asc - array is expected to be sorted in ascending order, first geq elem is matched
|
126
|
+
# * :asc_eq - array expected to be sorted in ascending order, first eq elem is matched
|
127
|
+
# * :desc - array is expected to be sorted in descending order, first leq elem is matched
|
128
|
+
# * :desc_eq - array is expected to be sorted in descending order, first eq elem is matched
|
129
|
+
#
|
130
|
+
# @param [Object] elem elem to search for
|
131
|
+
# @param [Fixnum] low lower bound (inclusive)
|
132
|
+
# @param [Fixnum] high upper bound (inclusive, nil for last element)
|
133
|
+
# @param [:asc, :desc, :asc_eq, :desc_eq] mode matching mode
|
134
|
+
# @return [Array] [index, first matching elem] in self, or nil if not found
|
135
|
+
#
|
136
|
+
def bin_assoc(elem, mode, low = 0, high = -1)
|
137
|
+
dir = if ::BinSearch::MODE_IS_ASC.include?(mode) then +1 else -1 end
|
138
|
+
check_eq = ::BinSearch::MODE_CHECK_EQ.include?(mode)
|
139
|
+
high = size - 1 if high < 0
|
140
|
+
if block_given?
|
141
|
+
then _bin_assoc(elem, low, high, dir, check_eq) { |b| yield elem, b }
|
142
|
+
else _bin_assoc(elem, low, high, dir, check_eq) { |b| elem <=> b } end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Binary search for the first elem in this array matching according to mode in the range
|
146
|
+
# (low..high-1)
|
147
|
+
#
|
148
|
+
# Elements are compared using <=> after mapping them using the provided block
|
149
|
+
#
|
150
|
+
# The supported modes are
|
151
|
+
# * :asc - array is expected to be sorted in ascending order, first geq elem is matched
|
152
|
+
# * :asc_eq - array expected to be sorted in ascending order, first eq elem is matched
|
153
|
+
# * :desc - array is expected to be sorted in descending order, first leq elem is matched
|
154
|
+
# * :desc_eq - array is expected to be sorted in descending order, first eq elem is matched
|
155
|
+
#
|
156
|
+
# @param [Object] elem elem to search for
|
157
|
+
# @param [Fixnum] low lower bound (inclusive)
|
158
|
+
# @param [Fixnum] high upper bound (inclusive, nil for last element)
|
159
|
+
# @param [:asc, :desc, :asc_eq, :desc_eq] mode matching mode
|
160
|
+
# @return [Array] [index, first matching elem] in self, or nil if not found
|
161
|
+
#
|
162
|
+
def bin_assoc_by(elem, mode, low = 0, high = -1)
|
163
|
+
dir = if ::BinSearch::MODE_IS_ASC.include?(mode) then +1 else -1 end
|
164
|
+
check_eq = ::BinSearch::MODE_CHECK_EQ.include?(mode)
|
165
|
+
high = size - 1 if high < 0
|
166
|
+
e = yield elem
|
167
|
+
_bin_assoc(elem, low, high, dir, check_eq) { |b| e <=> (yield b) }
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def _bin_index(elem, low, high, dir, check_eq)
|
173
|
+
sz = high - low
|
174
|
+
if (sz >> LIN_BITS).zero?
|
175
|
+
# On 2012 cpus, linear search is slightly faster than binary search
|
176
|
+
# if the number of searched elements is in the range of 50-100 elts
|
177
|
+
cur_index = low
|
178
|
+
while cur_index <= high
|
179
|
+
cur = self[cur_index]
|
180
|
+
cmp = yield cur
|
181
|
+
if cmp == dir
|
182
|
+
then cur_index += 1
|
183
|
+
else return (if (check_eq && cmp.nonzero?) then -1 else cur_index end) end
|
184
|
+
end
|
185
|
+
return -1
|
186
|
+
else
|
187
|
+
# Classic binary search
|
188
|
+
cmp_ = 0
|
189
|
+
last = -1
|
190
|
+
while low <= high
|
191
|
+
mid_index = low + (sz >> 1)
|
192
|
+
mid = self[mid_index]
|
193
|
+
cmp = yield mid
|
194
|
+
if cmp == dir
|
195
|
+
low = mid_index + 1
|
196
|
+
else
|
197
|
+
cmp_ = cmp
|
198
|
+
last = mid_index
|
199
|
+
high = mid_index - 1
|
200
|
+
end
|
201
|
+
sz -= 1
|
202
|
+
end
|
203
|
+
return (if (check_eq && cmp_.nonzero?) then -1 else last end)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def _bin_search(elem, low, high, dir, check_eq)
|
208
|
+
sz = high - low
|
209
|
+
cur = nil
|
210
|
+
if (sz >> LIN_BITS).zero?
|
211
|
+
# On 2012 cpus, linear search is slightly faster than binary search
|
212
|
+
# if the number of searched elements is in the range of 50-100 elts
|
213
|
+
cur_index = low
|
214
|
+
while cur_index <= high
|
215
|
+
cur = self[cur_index]
|
216
|
+
cmp = yield cur
|
217
|
+
if cmp == dir
|
218
|
+
then cur_index += 1
|
219
|
+
else return (if (check_eq && cmp.nonzero?) then nil else cur end) end
|
220
|
+
end
|
221
|
+
return nil
|
222
|
+
else
|
223
|
+
# Classic binary search
|
224
|
+
cmp_ = 0
|
225
|
+
last = -1
|
226
|
+
while low <= high
|
227
|
+
mid_index = low + (sz >> 1)
|
228
|
+
mid = self[mid_index]
|
229
|
+
cmp = yield mid
|
230
|
+
if cmp == dir
|
231
|
+
low = mid_index + 1
|
232
|
+
else
|
233
|
+
cmp_ = cmp
|
234
|
+
cur = mid
|
235
|
+
last = mid_index
|
236
|
+
high = mid_index - 1
|
237
|
+
end
|
238
|
+
sz -= 1
|
239
|
+
end
|
240
|
+
return (if (check_eq && cmp_.nonzero?) then nil else cur end)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def _bin_assoc(elem, low, high, dir, check_eq)
|
245
|
+
sz = high - low
|
246
|
+
cur = nil
|
247
|
+
if (sz >> LIN_BITS).zero?
|
248
|
+
# On 2012 cpus, linear search is slightly faster than binary search
|
249
|
+
# if the number of searched elements is in the range of 50-100 elts
|
250
|
+
cur_index = low
|
251
|
+
while cur_index <= high
|
252
|
+
cur = self[cur_index]
|
253
|
+
cmp = yield cur
|
254
|
+
if cmp == dir
|
255
|
+
cur_index += 1
|
256
|
+
else
|
257
|
+
return (if (!cur || (check_eq && cmp.nonzero?)) then nil else [cur_index, cur] end)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
return nil
|
261
|
+
else
|
262
|
+
# Classic binary search
|
263
|
+
cmp_ = 0
|
264
|
+
last = -1
|
265
|
+
while low <= high
|
266
|
+
mid_index = low + (sz >> 1)
|
267
|
+
mid = self[mid_index]
|
268
|
+
cmp = yield mid
|
269
|
+
if cmp == dir
|
270
|
+
low = mid_index + 1
|
271
|
+
else
|
272
|
+
cmp_ = cmp
|
273
|
+
cur = mid
|
274
|
+
last = mid_index
|
275
|
+
high = mid_index - 1
|
276
|
+
end
|
277
|
+
sz -= 1
|
278
|
+
end
|
279
|
+
return (if (!cur || (check_eq && cmp_.nonzero?)) then nil else [last, cur] end)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
end # module Methods
|
284
|
+
|
285
|
+
end # module BinSeach
|
286
|
+
|
287
|
+
class ::Array
|
288
|
+
include ::BinSearch::Methods
|
289
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'bin_search'
|
2
|
+
|
3
|
+
describe ::BinSearch do
|
4
|
+
|
5
|
+
context "Small arrays (:asc, :asc_eq)" do
|
6
|
+
|
7
|
+
it "should find the first element" do
|
8
|
+
[1, 2, 2, 3, 5].bin_index(1, :asc).should be_==(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should find the first element when queried using a smaller element" do
|
12
|
+
[1, 2, 2, 3, 5].bin_index(0, :asc).should be_==(0)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should find a middle element" do
|
16
|
+
[1, 2, 2, 3, 5].bin_index(2, :asc).should be_==(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should find the last element" do
|
20
|
+
[1, 2, 2, 3, 5].bin_index(5, :asc).should be_==(4)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not find a missing element" do
|
24
|
+
[1, 2, 2, 3, 5].bin_index(6, :asc).should be_==(-1)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not find an element that is not eq if called with equality checking turned on" do
|
28
|
+
[1, 2, 2, 3, 5].bin_index(2.5, :asc_eq).should be_==(-1)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context "Large arrays (:asc, :asc_eq)" do
|
34
|
+
|
35
|
+
it "should find the first element" do
|
36
|
+
Range.new(1, 10000).to_a.bin_index(1, :asc).should be_==(0)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should find the first element when queried using a smaller element" do
|
40
|
+
Range.new(1, 10000).to_a.bin_index(0, :asc).should be_==(0)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should find a middle element" do
|
44
|
+
Range.new(1, 10000).to_a.bin_index(5000, :asc).should be_==(4999)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should find the last element" do
|
48
|
+
Range.new(1, 10000).to_a.bin_index(10000, :asc).should be_==(9999)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not find a missing element" do
|
52
|
+
Range.new(1, 10000).to_a.bin_index(10001, :asc).should be_==(-1)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not find an element that is not eq if called with equality checking turned on" do
|
56
|
+
Range.new(1, 10000).to_a.bin_index(5000.5, :asc_eq).should be_==(-1)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
context "Small arrays (:desc, :desc_eq)" do
|
62
|
+
|
63
|
+
it "should find the first element" do
|
64
|
+
[1, 2, 2, 3, 5].reverse.bin_index(5, :desc).should be_==(0)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should find the first element when queried using a larger element" do
|
68
|
+
[1, 2, 2, 3, 5].bin_index(6, :desc).should be_==(0)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should find a middle element" do
|
72
|
+
[1, 2, 2, 3, 5].reverse.bin_index(2, :desc).should be_==(2)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should find the last element" do
|
76
|
+
[1, 2, 2, 3, 5].reverse.bin_index(1, :desc).should be_==(4)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should not find a missing element" do
|
80
|
+
[1, 2, 2, 3, 5].reverse.bin_index(0, :desc).should be_==(-1)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should not find an element that is not eq if called with equality checking turned on" do
|
84
|
+
[1, 2, 2, 3, 5].reverse.bin_index(2.5, :desc_eq).should be_==(-1)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
context "Large arrays (:desc, :desc_eq)" do
|
90
|
+
|
91
|
+
it "should find the first element" do
|
92
|
+
Range.new(1, 10000).to_a.reverse.bin_index(10000, :desc).should be_==(0)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should find the first element when queried using a larger element" do
|
96
|
+
Range.new(1, 10000).to_a.reverse.bin_index(10001, :desc).should be_==(0)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should find a middle element" do
|
100
|
+
Range.new(1, 10000).to_a.reverse.bin_index(5000, :desc).should be_==(5000)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should find the last element" do
|
104
|
+
Range.new(1, 10000).to_a.reverse.bin_index(1, :desc).should be_==(9999)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should not find a missing element" do
|
108
|
+
Range.new(1, 10000).to_a.reverse.bin_index(0, :desc).should be_==(-1)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should not find an element that is not eq if called with equality checking turned on" do
|
112
|
+
Range.new(1, 10000).to_a.reverse.bin_index(5000.5, :desc_eq).should be_==(-1)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
context "API" do
|
118
|
+
|
119
|
+
it "should return the found element when called via bin_search" do
|
120
|
+
[1, 2, 2, 3, 5].bin_search(2, :asc).should be_==(2)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should return nil when called via bin_search with a missing element" do
|
124
|
+
[1, 2, 2, 3, 5].bin_search(10, :asc).should be_==(nil)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return the found element's assoc pair when called via bin_assoc" do
|
128
|
+
[1, 2, 2, 3, 5].bin_assoc(2, :asc).should be_==([1, 2])
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should return nil when called via bin_assoc with a missing element" do
|
132
|
+
[1, 2, 2, 3, 5].bin_assoc(10, :asc).should be_==(nil)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
context "Comparator wrapping" do
|
138
|
+
|
139
|
+
it 'should use a provided comparator with bin_index' do
|
140
|
+
idx = [1, 2, 2, 3, 5].reverse.bin_index(2, :asc) { |a, b| b <=> a }
|
141
|
+
idx.should be_==(2)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should use a provided mapper with bin_index_by' do
|
145
|
+
idx = [1, 2, 2, 3, 5].reverse.bin_index_by(2, :asc) { |a| -a }
|
146
|
+
idx.should be_==(2)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bin_search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Stefan Plantikow
|
9
|
+
autorequire:
|
10
|
+
bindir: script
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70278990982880 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70278990982880
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: simplecov
|
27
|
+
requirement: &70278990982460 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70278990982460
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70278973891460 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70278973891460
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: yard
|
49
|
+
requirement: &70278973891040 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70278973891040
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: redcarpet
|
60
|
+
requirement: &70278973890620 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70278973890620
|
69
|
+
description: Pure ruby implementation of binary search for Ruby arrays and similiar
|
70
|
+
data structures. Supports ascending and descending sort order, searching for exact
|
71
|
+
and nearest matches, and has a versatile API. Uses linear search for small arrays
|
72
|
+
to make use of the internal cache of moden CPUs.
|
73
|
+
email: stefan.plantikow@googlemail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- .gitignore
|
79
|
+
- AUTHORS
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin_search.gemspec
|
85
|
+
- lib/bin_search.rb
|
86
|
+
- lib/bin_search/bin_search.rb
|
87
|
+
- lib/bin_search/version.rb
|
88
|
+
- spec/lib/bin_search/bin_search_spec.rb
|
89
|
+
- spec/spec_helper.rb
|
90
|
+
homepage: https://github.com/boggle/bin_search
|
91
|
+
licenses:
|
92
|
+
- PUBLIC DOMAIN WITHOUT ANY WARRANTY
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project: bin_search
|
111
|
+
rubygems_version: 1.8.10
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Binary search in sorted arrays
|
115
|
+
test_files:
|
116
|
+
- spec/lib/bin_search/bin_search_spec.rb
|
117
|
+
- spec/spec_helper.rb
|
118
|
+
has_rdoc:
|