tyler-binary_search 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +26 -12
- data/VERSION.yml +1 -1
- data/lib/binary_search/pure.rb +27 -10
- metadata +1 -2
- data/ext/binary_search.c +0 -28
data/README.textile
CHANGED
@@ -2,6 +2,32 @@ h1. Binary Search for Ruby's Arrays
|
|
2
2
|
|
3
3
|
One incredibly handy algorithm that is missing from Ruby's Array class is the binary search. If we *know* for *absolute certain* that the array we're working with is sorted you can use a binary search to search through the array much much more quickly than a linear search, which would be performed with index or detect/find.
|
4
4
|
|
5
|
+
h2. Usage
|
6
|
+
|
7
|
+
There are two methods defined by this gem. binary_search and binary_index. There are two versions of both of those methods. You can use the native version by requiring 'binary_search/native' or use the pure Ruby version with 'binary_search/pure'.
|
8
|
+
|
9
|
+
<pre>
|
10
|
+
<code>
|
11
|
+
require 'binary_search/native'
|
12
|
+
|
13
|
+
x = [5,1,6,7,2,6,4,2,6,1,6,1,1,8,3,5,2].sort
|
14
|
+
puts x.binary_index(5)
|
15
|
+
#=> 10
|
16
|
+
|
17
|
+
target = 4
|
18
|
+
y = [[1,:a], [2,:b], [3,:c], [4,:d]]
|
19
|
+
puts x.binary_search { |v| target <=> v }
|
20
|
+
#=> [4,:d]
|
21
|
+
</code>
|
22
|
+
</pre>
|
23
|
+
|
24
|
+
So the method 'binary_index' does the same thing that 'index' does: returns the index of a matching element. It should be noted that 'index' returns the *first* instance of a matching element. 'binary_index' is not guaranteed to return the first. It should also be noted, again, that this will only work if the array is sorted correctly. If it's not weird crap will happen.
|
25
|
+
|
26
|
+
'binary_search' is similar to 'find' or 'detect' from Ruby's normal arsenal. The difference is that rather than the block needing to return a boolean, it needs to return the usual output from the '<=>' operator. (1 for >, -1 for <, and 0 for ==). This is obviously because we need to know whether the element being evaluated is greater than, less than, or equal to the value we're actually looking for.
|
27
|
+
|
28
|
+
|
29
|
+
h2. Benchmarks
|
30
|
+
|
5
31
|
Need proof? Howsabout some benchmarks:
|
6
32
|
|
7
33
|
<pre>
|
@@ -74,15 +100,3 @@ Native BI: 0.000000 0.000000 0.000000 ( 0.001602)
|
|
74
100
|
|
75
101
|
So, your array must be fairly large (between 100 and 1000 elements) for the Ruby version of binary_index to be faster than Ruby's builtin index method. However, even for arrays as small as 5 elements, the native version of the binary_index method is faster than Ruby's index. However, for very large sized Arrays, both the the pure and the native version are much much much faster than the builtin method.
|
76
102
|
|
77
|
-
<pre>
|
78
|
-
<code>
|
79
|
-
require 'binary_search/native'
|
80
|
-
|
81
|
-
x = [5,1,6,7,2,6,4,2,6,1,6,1,1,8,3,5,2].sort
|
82
|
-
puts x.binary_index(5)
|
83
|
-
</code>
|
84
|
-
</pre>
|
85
|
-
|
86
|
-
So the actual method is 'binary_index' as it does the same thing that 'index' does: returns the index of a matching element. It should be noted that 'index' returns the *first* instance of a matching element. 'binary_index' is not guaranteed to return the first. It should also be noted, again, that this will only work if the array is sorted correctly. If it's not weird crap will happen.
|
87
|
-
|
88
|
-
Oh yeah, and don't bother trying to require 'binary_search', it'll just throw an error telling you to either require 'binary_search/pure' or 'binary_search/native'. I'd always use native... but some people are weird.
|
data/VERSION.yml
CHANGED
data/lib/binary_search/pure.rb
CHANGED
@@ -1,15 +1,32 @@
|
|
1
1
|
class Array
|
2
|
-
def binary_index(target
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
2
|
+
def binary_index(target)
|
3
|
+
binary_chop { |v| target <=> v }
|
4
|
+
end
|
5
|
+
|
6
|
+
def binary_search(&block)
|
7
|
+
index = binary_chop(&block)
|
8
|
+
index ? self[index] : nil
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def binary_chop(&block)
|
14
|
+
upper = self.size - 1
|
15
|
+
lower = 0
|
16
|
+
|
17
|
+
while(upper >= lower) do
|
18
|
+
idx = lower + (upper - lower) / 2
|
19
|
+
comp = yield self[idx]
|
20
|
+
|
21
|
+
if comp == 0
|
22
|
+
return idx
|
23
|
+
elsif comp > 0
|
24
|
+
lower = idx + 1
|
25
|
+
else
|
26
|
+
upper = idx - 1
|
27
|
+
end
|
12
28
|
end
|
29
|
+
nil
|
13
30
|
end
|
14
31
|
end
|
15
32
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tyler-binary_search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tyler McMullen
|
@@ -24,7 +24,6 @@ extra_rdoc_files: []
|
|
24
24
|
files:
|
25
25
|
- README.textile
|
26
26
|
- VERSION.yml
|
27
|
-
- ext/binary_search.c
|
28
27
|
- ext/extconf.rb
|
29
28
|
- lib/binary_search
|
30
29
|
- lib/binary_search/native.rb
|
data/ext/binary_search.c
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
#include <ruby.h>
|
2
|
-
|
3
|
-
static ID id_cmp;
|
4
|
-
|
5
|
-
static VALUE rb_array_binary_index(VALUE self, VALUE value) {
|
6
|
-
int lower = 0;
|
7
|
-
int upper = RARRAY(self)->len - 1;
|
8
|
-
int i, comp;
|
9
|
-
|
10
|
-
while(lower <= upper) {
|
11
|
-
i = lower + (upper - lower) / 2;
|
12
|
-
comp = FIX2INT(rb_funcall(value, id_cmp, 1, RARRAY(self)->ptr[i]));
|
13
|
-
|
14
|
-
if(comp == 0) {
|
15
|
-
return LONG2NUM(i);
|
16
|
-
} else if(comp == 1) {
|
17
|
-
lower = i + 1;
|
18
|
-
} else {
|
19
|
-
upper = i - 1;
|
20
|
-
};
|
21
|
-
}
|
22
|
-
return Qnil;
|
23
|
-
}
|
24
|
-
|
25
|
-
void Init_binary_search() {
|
26
|
-
id_cmp = rb_intern("<=>");
|
27
|
-
rb_define_method(rb_cArray, "binary_index", rb_array_binary_index, 1);
|
28
|
-
}
|