mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
range.c: Make Range#bsearch support endless ranges
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@63195 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
db1bdecb0d
commit
d658a8d568
2 changed files with 74 additions and 46 deletions
115
range.c
115
range.c
|
@ -18,7 +18,7 @@
|
|||
#include <math.h>
|
||||
|
||||
VALUE rb_cRange;
|
||||
static ID id_beg, id_end, id_excl, id_integer_p, id_div;
|
||||
static ID id_beg, id_end, id_excl, id_integer_p, id_add, id_mul, id_div;
|
||||
#define id_cmp idCmp
|
||||
#define id_succ idSucc
|
||||
|
||||
|
@ -526,6 +526,62 @@ is_integer_p(VALUE v)
|
|||
return RTEST(is_int) && is_int != Qundef;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
bsearch_integer_range(VALUE beg, VALUE end, int excl)
|
||||
{
|
||||
VALUE satisfied = Qnil;
|
||||
int smaller;
|
||||
|
||||
#define BSEARCH_CHECK(expr) \
|
||||
do { \
|
||||
VALUE val = (expr); \
|
||||
VALUE v = rb_yield(val); \
|
||||
if (FIXNUM_P(v)) { \
|
||||
if (v == INT2FIX(0)) return val; \
|
||||
smaller = (SIGNED_VALUE)v < 0; \
|
||||
} \
|
||||
else if (v == Qtrue) { \
|
||||
satisfied = val; \
|
||||
smaller = 1; \
|
||||
} \
|
||||
else if (v == Qfalse || v == Qnil) { \
|
||||
smaller = 0; \
|
||||
} \
|
||||
else if (rb_obj_is_kind_of(v, rb_cNumeric)) { \
|
||||
int cmp = rb_cmpint(rb_funcall(v, id_cmp, 1, INT2FIX(0)), v, INT2FIX(0)); \
|
||||
if (!cmp) return val; \
|
||||
smaller = cmp < 0; \
|
||||
} \
|
||||
else { \
|
||||
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE \
|
||||
" (must be numeric, true, false or nil)", \
|
||||
rb_obj_class(v)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
VALUE low = rb_to_int(beg);
|
||||
VALUE high = rb_to_int(end);
|
||||
VALUE mid, org_high;
|
||||
if (excl) 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), id_div, 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;
|
||||
}
|
||||
return satisfied;
|
||||
}
|
||||
|
||||
/*
|
||||
* call-seq:
|
||||
* rng.bsearch {|obj| block } -> value
|
||||
|
@ -598,33 +654,6 @@ range_bsearch(VALUE range)
|
|||
* (-1...0.0).bsearch to yield -0.0.
|
||||
*/
|
||||
|
||||
#define BSEARCH_CHECK(expr) \
|
||||
do { \
|
||||
VALUE val = (expr); \
|
||||
VALUE v = rb_yield(val); \
|
||||
if (FIXNUM_P(v)) { \
|
||||
if (v == INT2FIX(0)) return val; \
|
||||
smaller = (SIGNED_VALUE)v < 0; \
|
||||
} \
|
||||
else if (v == Qtrue) { \
|
||||
satisfied = val; \
|
||||
smaller = 1; \
|
||||
} \
|
||||
else if (v == Qfalse || v == Qnil) { \
|
||||
smaller = 0; \
|
||||
} \
|
||||
else if (rb_obj_is_kind_of(v, rb_cNumeric)) { \
|
||||
int cmp = rb_cmpint(rb_funcall(v, id_cmp, 1, INT2FIX(0)), v, INT2FIX(0)); \
|
||||
if (!cmp) return val; \
|
||||
smaller = cmp < 0; \
|
||||
} \
|
||||
else { \
|
||||
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE \
|
||||
" (must be numeric, true, false or nil)", \
|
||||
rb_obj_class(v)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define BSEARCH(conv) \
|
||||
do { \
|
||||
RETURN_ENUMERATOR(range, 0, 0); \
|
||||
|
@ -661,34 +690,26 @@ range_bsearch(VALUE range)
|
|||
#if SIZEOF_DOUBLE == 8 && defined(HAVE_INT64_T)
|
||||
else if (RB_TYPE_P(beg, T_FLOAT) || RB_TYPE_P(end, T_FLOAT)) {
|
||||
int64_t low = double_as_int64(RFLOAT_VALUE(rb_Float(beg)));
|
||||
int64_t high = double_as_int64(RFLOAT_VALUE(rb_Float(end)));
|
||||
int64_t high = double_as_int64(NIL_P(end) ? HUGE_VAL : RFLOAT_VALUE(rb_Float(end)));
|
||||
int64_t mid, org_high;
|
||||
BSEARCH(int64_as_double_to_num);
|
||||
}
|
||||
#endif
|
||||
else if (is_integer_p(beg) && is_integer_p(end)) {
|
||||
VALUE low = rb_to_int(beg);
|
||||
VALUE high = rb_to_int(end);
|
||||
VALUE mid, org_high;
|
||||
RETURN_ENUMERATOR(range, 0, 0);
|
||||
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), id_div, 1, INT2FIX(2));
|
||||
return bsearch_integer_range(beg, end, EXCL(range));
|
||||
}
|
||||
else if (is_integer_p(beg) && NIL_P(end)) {
|
||||
VALUE diff = LONG2FIX(1);
|
||||
RETURN_ENUMERATOR(range, 0, 0);
|
||||
while (1) {
|
||||
VALUE mid = rb_funcall(beg, id_add, 1, diff);
|
||||
BSEARCH_CHECK(mid);
|
||||
if (smaller) {
|
||||
high = mid;
|
||||
}
|
||||
else {
|
||||
low = rb_funcall(mid, '+', 1, INT2FIX(1));
|
||||
return bsearch_integer_range(beg, mid, 0);
|
||||
}
|
||||
diff = rb_funcall(diff, id_mul, 1, LONG2FIX(2));
|
||||
}
|
||||
if (rb_equal(low, org_high)) {
|
||||
BSEARCH_CHECK(low);
|
||||
if (!smaller) return Qnil;
|
||||
}
|
||||
return satisfied;
|
||||
}
|
||||
else {
|
||||
rb_raise(rb_eTypeError, "can't do binary search for %s", rb_obj_classname(beg));
|
||||
|
@ -1363,6 +1384,8 @@ Init_Range(void)
|
|||
id_end = rb_intern("end");
|
||||
id_excl = rb_intern("excl");
|
||||
id_integer_p = rb_intern("integer?");
|
||||
id_add = rb_intern("+");
|
||||
id_mul = rb_intern("*");
|
||||
id_div = rb_intern("div");
|
||||
|
||||
rb_cRange = rb_struct_define_without_accessor(
|
||||
|
|
|
@ -581,6 +581,8 @@ class TestRange < Test::Unit::TestCase
|
|||
|
||||
ary = [0, 100, 100, 100, 200]
|
||||
assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 })
|
||||
|
||||
assert_equal(1_000_001, (0...).bsearch {|i| i > 1_000_000 })
|
||||
end
|
||||
|
||||
def test_bsearch_for_float
|
||||
|
@ -632,6 +634,8 @@ class TestRange < Test::Unit::TestCase
|
|||
|
||||
assert_in_delta(1.0, (0.0..inf).bsearch {|x| Math.log(x) >= 0 })
|
||||
assert_in_delta(7.0, (0.0..10).bsearch {|x| 7.0 - x })
|
||||
|
||||
assert_equal(1_000_000.0.next_float, (0.0..).bsearch {|x| x > 1_000_000 })
|
||||
end
|
||||
|
||||
def check_bsearch_values(range, search, a)
|
||||
|
@ -733,6 +737,7 @@ class TestRange < Test::Unit::TestCase
|
|||
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_equal(bignum * 2 + 1, (bignum...).bsearch {|i| i > bignum * 2 })
|
||||
|
||||
assert_raise(TypeError) { ("a".."z").bsearch {} }
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue