bin_search 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.
- 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:
|