Feature #4766 ยป bsearch.patch
array.c | ||
---|---|---|
return ary;
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* ary.bsearch {|x| block } -> elem
|
||
*/
|
||
static VALUE
|
||
rb_ary_bsearch(VALUE ary)
|
||
{
|
||
long low = 0, high = RARRAY_LEN(ary), mid;
|
||
int smaller, satisfied = 0;
|
||
VALUE v, val;
|
||
while (low < high) {
|
||
mid = low + ((high - low) / 2);
|
||
val = rb_ary_entry(ary, mid);
|
||
v = rb_yield(val);
|
||
if (FIXNUM_P(v)) {
|
||
if (FIX2INT(v) == 0) return val;
|
||
smaller = FIX2INT(v) < 0;
|
||
}
|
||
else if (v == Qtrue) {
|
||
satisfied = 1;
|
||
smaller = 1;
|
||
}
|
||
else if (v == Qfalse || v == Qnil) {
|
||
smaller = 0;
|
||
}
|
||
else if (rb_obj_is_kind_of(v, rb_cNumeric)) {
|
||
switch (rb_cmpint(rb_funcall(v, id_cmp, 1, INT2FIX(0)), v, INT2FIX(0)) < 0) {
|
||
case 0: return val;
|
||
case 1: smaller = 1;
|
||
case -1: smaller = 0;
|
||
}
|
||
}
|
||
else {
|
||
smaller = RTEST(v);
|
||
}
|
||
if (smaller) {
|
||
high = mid;
|
||
}
|
||
else {
|
||
low = mid + 1;
|
||
}
|
||
}
|
||
if (low == RARRAY_LEN(ary)) return Qnil;
|
||
if (!satisfied) return Qnil;
|
||
return rb_ary_entry(ary, low);
|
||
}
|
||
static VALUE
|
||
sort_by_i(VALUE i)
|
||
... | ... | |
rb_define_method(rb_cArray, "take_while", rb_ary_take_while, 0);
|
||
rb_define_method(rb_cArray, "drop", rb_ary_drop, 1);
|
||
rb_define_method(rb_cArray, "drop_while", rb_ary_drop_while, 0);
|
||
rb_define_method(rb_cArray, "bsearch", rb_ary_bsearch, 0);
|
||
id_cmp = rb_intern("<=>");
|
||
sym_random = ID2SYM(rb_intern("random"));
|
range.c | ||
---|---|---|
#include "ruby/encoding.h"
|
||
#include "internal.h"
|
||
#ifdef HAVE_FLOAT_H
|
||
#include <float.h>
|
||
#endif
|
||
#include <math.h>
|
||
VALUE rb_cRange;
|
||
static ID id_cmp, id_succ, id_beg, id_end, id_excl;
|
||
... | ... | |
return range;
|
||
}
|
||
/*
|
||
* call-seq:
|
||
* rng.bsearch {|obj| block } -> element
|
||
*
|
||
* Finds a value in range which meets the given condition in O(n log n)
|
||
* where n = (rng.begin - rng.end), by using binary search.
|
||
*
|
||
* The given block receives a current value, determines if it meets the
|
||
* condition and controls search.
|
||
* When the condition is satisfied and you want to stop search, the block
|
||
* should return zero, and then this method return the value immediately.
|
||
* When the condition is satisfied and you want to find minimum bound,
|
||
* the block should return true. When the condition is not satisfied and
|
||
* the current value is smaller than wanted, the block should return false,
|
||
* nil or an integer greater than zero. When the condition is not satisfied
|
||
* and the current value is larger than wanted, the block should return an
|
||
* integer less than zero.
|
||
* Unless the block returns zero, the search will continue until a minimum
|
||
* bound is found or no match is found. Returns the minimum bound if any,
|
||
* or returns nil when no match is found.
|
||
*
|
||
* The block must be monotone; there must be two values a and b so that
|
||
* the block returns:
|
||
* - false, nil or an integer greater than zero for all x of [begin of
|
||
* range, a), and
|
||
* - zero or true for all x of [a, b), and
|
||
* - an integer less than zero for all x of [b, end of range).
|
||
* If the block is not monotone, the result is unspecified.
|
||
*
|
||
* This method takes O(n log n), but it is unspecified which value is
|
||
* actually picked up at each iteration.
|
||
*
|
||
* ary = [0, 4, 7, 10, 12]
|
||
* (0...ary.size).bsearch {|i| ary[i] >= 4 } #=> 1
|
||
* (0...ary.size).bsearch {|i| ary[i] >= 6 } #=> 2
|
||
* (0...ary.size).bsearch {|i| ary[i] >= 8 } #=> 3
|
||
* (0...ary.size).bsearch {|i| ary[i] >= 100 } #=> nil
|
||
*
|
||
* (0.0...Float::INFINITY).bsearch {|x| Math.log(x) >= 0 } #=> 1.0
|
||
*
|
||
* ary = [0, 100, 100, 100, 200]
|
||
* (0..4).bsearch {|i| 100 - i } #=> 1, 2 or 3
|
||
* (0..4).bsearch {|i| 300 - i } #=> nil
|
||
* (0..4).bsearch {|i| 50 - i } #=> nil
|
||
*/
|
||
static VALUE
|
||
range_bsearch(VALUE range)
|
||
{
|
||
VALUE beg, end;
|
||
int smaller, satisfied = 0;
|
||
#define BSEARCH_CHECK(val) \
|
||
do { \
|
||
VALUE v = rb_yield(val); \
|
||
if (FIXNUM_P(v)) { \
|
||
if (FIX2INT(v) == 0) return val; \
|
||
smaller = FIX2INT(v) < 0; \
|
||
} \
|
||
else if (v == Qtrue) { \
|
||
satisfied = 1; \
|
||
smaller = 1; \
|
||
} \
|
||
else if (v == Qfalse || v == Qnil) { \
|
||
smaller = 0; \
|
||
} \
|
||
else if (rb_obj_is_kind_of(v, rb_cNumeric)) { \
|
||
switch (rb_cmpint(rb_funcall(v, id_cmp, 1, INT2FIX(0)), v, INT2FIX(0)) < 0) { \
|
||
case 0: return val; \
|
||
case 1: smaller = 1; \
|
||
case -1: smaller = 0; \
|
||
} \
|
||
} \
|
||
else { \
|
||
smaller = RTEST(v); \
|
||
} \
|
||
} while (0)
|
||
beg = RANGE_BEG(range);
|
||
end = RANGE_END(range);
|
||
if (FIXNUM_P(beg) && FIXNUM_P(end)) {
|
||
long low = FIX2LONG(beg);
|
||
long high = FIX2LONG(end);
|
||
long mid, org_high;
|
||
if (EXCL(range)) high--;
|
||
org_high = high;
|
||
while (low < high) {
|
||
mid = low + ((high - low) / 2);
|
||
BSEARCH_CHECK(INT2FIX(mid));
|
||
if (smaller) {
|
||
high = mid;
|
||
}
|
||
else {
|
||
low = mid + 1;
|
||
}
|
||
}
|
||
if (low == org_high) {
|
||
BSEARCH_CHECK(INT2FIX(low));
|
||
if (!smaller) return Qnil;
|
||
}
|
||
if (!satisfied) return Qnil;
|
||
return INT2FIX(low);
|
||
}
|
||
else if (TYPE(beg) == T_FLOAT || TYPE(end) == T_FLOAT) {
|
||
double low = RFLOAT_VALUE(rb_Float(beg));
|
||
double high = RFLOAT_VALUE(rb_Float(end));
|
||
double mid, org_high, last_found_key = 0.0;
|
||
int count, found = 0;
|
||
#ifdef FLT_RADIX
|
||
#ifdef DBL_MANT_DIG
|
||
#define COUNT (((FLT_RADIX) - 1) * (DBL_MANT_DIG + DBL_MAX_EXP) + 100)
|
||
#else
|
||
#define count (53 + 1023 + 100)
|
||
#endif
|
||
#else
|
||
#define count (53 + 1023 + 100)
|
||
#endif
|
||
if (isinf(high) && high > 0) {
|
||
double nhigh = 1.0, inc;
|
||
if (nhigh < low) nhigh = low;
|
||
count = COUNT;
|
||
while (count >= 0 && !isinf(nhigh)) {
|
||
BSEARCH_CHECK(DBL2NUM(nhigh));
|
||
if (smaller) break;
|
||
high = nhigh;
|
||
nhigh *= 2;
|
||
count--;
|
||
}
|
||
if (isinf(nhigh) || count < 0) {
|
||
inc = high / 2;
|
||
count = COUNT;
|
||
while (count >= 0 && inc > 0) {
|
||
nhigh = high + inc;
|
||
if (!isinf(nhigh)) {
|
||
BSEARCH_CHECK(DBL2NUM(nhigh));
|
||
if (smaller) {
|
||
low = high;
|
||
high = nhigh;
|
||
goto binsearch;
|
||
}
|
||
else {
|
||
high = nhigh;
|
||
}
|
||
}
|
||
inc /= 2;
|
||
count--;
|
||
}
|
||
high *= 2; /* generate infinity */
|
||
if (isinf(high) && !EXCL(range)) {
|
||
BSEARCH_CHECK(DBL2NUM(high));
|
||
if (!satisfied) return Qnil;
|
||
if (smaller) return DBL2NUM(high);
|
||
}
|
||
return Qnil;
|
||
}
|
||
high = nhigh;
|
||
}
|
||
if (isinf(low) && low < 0) {
|
||
double nlow = -1.0, dec;
|
||
if (nlow > high) nlow = high;
|
||
count = COUNT;
|
||
while (count >= 0 && !isinf(nlow)) {
|
||
BSEARCH_CHECK(DBL2NUM(nlow));
|
||
if (!smaller) break;
|
||
low = nlow;
|
||
nlow *= 2;
|
||
count--;
|
||
}
|
||
if (isinf(nlow) || count < 0) {
|
||
dec = low / 2;
|
||
count = COUNT;
|
||
while (count >= 0 && dec < 0) {
|
||
nlow = low + dec;
|
||
if (!isinf(nlow)) {
|
||
BSEARCH_CHECK(DBL2NUM(nlow));
|
||
if (!smaller) {
|
||
high = low;
|
||
low = nlow;
|
||
goto binsearch;
|
||
}
|
||
else {
|
||
low = nlow;
|
||
}
|
||
}
|
||
dec /= 2;
|
||
count--;
|
||
}
|
||
nlow = low * 2; /* generate infinity */
|
||
if (isinf(nlow)) {
|
||
BSEARCH_CHECK(DBL2NUM(nlow));
|
||
if (!satisfied) return Qnil;
|
||
if (smaller) return DBL2NUM(nlow);
|
||
}
|
||
if (!satisfied) return Qnil;
|
||
return DBL2NUM(low);
|
||
}
|
||
low = nlow;
|
||
}
|
||
binsearch:
|
||
org_high = high;
|
||
count = COUNT;
|
||
while (low < high && count >= 0) {
|
||
mid = low + ((high - low) / 2);
|
||
BSEARCH_CHECK(DBL2NUM(mid));
|
||
if (smaller) {
|
||
found = 1;
|
||
last_found_key = high;
|
||
high = mid;
|
||
}
|
||
else {
|
||
low = mid;
|
||
}
|
||
count--;
|
||
}
|
||
BSEARCH_CHECK(DBL2NUM(low));
|
||
if (!smaller) {
|
||
BSEARCH_CHECK(DBL2NUM(high));
|
||
if (!smaller) {
|
||
if (found) {
|
||
low = last_found_key;
|
||
}
|
||
else {
|
||
return Qnil;
|
||
}
|
||
}
|
||
low = high;
|
||
}
|
||
if (!satisfied) return Qnil;
|
||
if (EXCL(range) && low >= org_high) return Qnil;
|
||
return DBL2NUM(low);
|
||
#undef COUNT
|
||
}
|
||
else if (!NIL_P(rb_check_to_integer(beg, "to_int")) &&
|
||
!NIL_P(rb_check_to_integer(end, "to_int"))) {
|
||
VALUE low = beg;
|
||
VALUE high = end;
|
||
VALUE mid, org_high;
|
||
if (EXCL(range)) high = rb_funcall(high, '-', 1, INT2FIX(1));
|
||
org_high = high;
|
||
while (rb_cmpint(rb_funcall(low, id_cmp, 1, high), low, high) < 0) {
|
||
mid = rb_funcall(rb_funcall(high, '+', 1, low), '/', 1, INT2FIX(2));
|
||
BSEARCH_CHECK(mid);
|
||
if (smaller) {
|
||
high = mid;
|
||
}
|
||
else {
|
||
low = rb_funcall(mid, '+', 1, INT2FIX(1));
|
||
}
|
||
}
|
||
if (rb_equal(low, org_high)) {
|
||
BSEARCH_CHECK(low);
|
||
if (!smaller) return Qnil;
|
||
}
|
||
if (!satisfied) return Qnil;
|
||
return low;
|
||
}
|
||
else {
|
||
rb_raise(rb_eTypeError, "can't do binary search for %s", rb_obj_classname(beg));
|
||
}
|
||
return range;
|
||
|
||
}
|
||
static VALUE
|
||
each_i(VALUE v, void *arg)
|
||
{
|
||
... | ... | |
rb_define_method(rb_cRange, "hash", range_hash, 0);
|
||
rb_define_method(rb_cRange, "each", range_each, 0);
|
||
rb_define_method(rb_cRange, "step", range_step, -1);
|
||
rb_define_method(rb_cRange, "bsearch", range_bsearch, 0);
|
||
rb_define_method(rb_cRange, "begin", range_begin, 0);
|
||
rb_define_method(rb_cRange, "end", range_end, 0);
|
||
rb_define_method(rb_cRange, "first", range_first, -1);
|
test/ruby/test_range.rb | ||
---|---|---|
assert !x.eql?(z)
|
||
}
|
||
end
|
||
def test_bsearch_for_fixnum
|
||
ary = [3, 4, 7, 9, 12]
|
||
assert_equal(0, (0...ary.size).bsearch {|i| ary[i] >= 2 })
|
||
assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 4 })
|
||
assert_equal(2, (0...ary.size).bsearch {|i| ary[i] >= 6 })
|
||
assert_equal(3, (0...ary.size).bsearch {|i| ary[i] >= 8 })
|
||
assert_equal(4, (0...ary.size).bsearch {|i| ary[i] >= 10 })
|
||
assert_equal(nil, (0...ary.size).bsearch {|i| ary[i] >= 100 })
|
||
assert_equal(0, (0...ary.size).bsearch {|i| true })
|
||
assert_equal(nil, (0...ary.size).bsearch {|i| false })
|
||
ary = [0, 100, 100, 100, 200]
|
||
assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 })
|
||
end
|
||
def test_bsearch_for_float
|
||
inf = Float::INFINITY
|
||
assert_in_delta(10.0, (0.0...100.0).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001)
|
||
assert_in_delta(10.0, (0.0...inf).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001)
|
||
assert_in_delta(-10.0, (-inf..100.0).bsearch {|x| x >= 0 || Math.log(-x / 10) < 0 }, 0.0001)
|
||
assert_in_delta(10.0, (-inf..inf).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001)
|
||
assert_equal(nil, (-inf..5).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001)
|
||
assert_in_delta(10.0, (-inf.. 10).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001)
|
||
assert_equal(nil, (-inf...10).bsearch {|x| x > 0 && Math.log(x / 10) >= 0 }, 0.0001)
|
||
assert_equal(nil, (-inf..inf).bsearch { false })
|
||
assert_equal(-inf, (-inf..inf).bsearch { true })
|
||
assert_equal(inf, (0..inf).bsearch {|x| x == inf })
|
||
assert_equal(nil, (0...inf).bsearch {|x| x == inf })
|
||
v = (-inf..0).bsearch {|x| x != -inf }
|
||
assert_operator(-Float::MAX, :>=, v)
|
||
assert_operator(-inf, :<, v)
|
||
v = (0.0..1.0).bsearch {|x| x > 0 } # the nearest positive value to 0.0
|
||
assert_in_delta(0, v, 0.0001)
|
||
assert_operator(0, :<, v)
|
||
assert_equal(0.0, (-1.0..0.0).bsearch {|x| x >= 0 })
|
||
assert_equal(nil, (-1.0...0.0).bsearch {|x| x >= 0 })
|
||
v = (0..Float::MAX).bsearch {|x| x >= Float::MAX }
|
||
assert_in_delta(Float::MAX, v)
|
||
assert_equal(nil, v.infinite?)
|
||
v = (0..inf).bsearch {|x| x >= Float::MAX }
|
||
assert_in_delta(Float::MAX, v)
|
||
assert_equal(nil, v.infinite?)
|
||
v = (-Float::MAX..0).bsearch {|x| x > -Float::MAX }
|
||
assert_operator(-Float::MAX, :<, v)
|
||
assert_equal(nil, v.infinite?)
|
||
v = (-inf..0).bsearch {|x| x >= -Float::MAX }
|
||
assert_in_delta(-Float::MAX, v)
|
||
assert_equal(nil, v.infinite?)
|
||
end
|
||
def test_bsearch_for_bignum
|
||
bignum = 2**100
|
||
ary = [3, 4, 7, 9, 12]
|
||
assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 2 })
|
||
assert_equal(bignum + 1, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 4 })
|
||
assert_equal(bignum + 2, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 6 })
|
||
assert_equal(bignum + 3, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 8 })
|
||
assert_equal(bignum + 4, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 10 })
|
||
assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 100 })
|
||
assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true })
|
||
assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| false })
|
||
assert_raise(TypeError) { ("a".."z").bsearch {} }
|
||
end
|
||
end
|