1
0
Fork 0
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:
mame 2018-04-19 15:18:57 +00:00
parent db1bdecb0d
commit d658a8d568
2 changed files with 74 additions and 46 deletions

115
range.c
View file

@ -18,7 +18,7 @@
#include <math.h> #include <math.h>
VALUE rb_cRange; 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_cmp idCmp
#define id_succ idSucc #define id_succ idSucc
@ -526,6 +526,62 @@ is_integer_p(VALUE v)
return RTEST(is_int) && is_int != Qundef; 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: * call-seq:
* rng.bsearch {|obj| block } -> value * rng.bsearch {|obj| block } -> value
@ -598,33 +654,6 @@ range_bsearch(VALUE range)
* (-1...0.0).bsearch to yield -0.0. * (-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) \ #define BSEARCH(conv) \
do { \ do { \
RETURN_ENUMERATOR(range, 0, 0); \ RETURN_ENUMERATOR(range, 0, 0); \
@ -661,35 +690,27 @@ range_bsearch(VALUE range)
#if SIZEOF_DOUBLE == 8 && defined(HAVE_INT64_T) #if SIZEOF_DOUBLE == 8 && defined(HAVE_INT64_T)
else if (RB_TYPE_P(beg, T_FLOAT) || RB_TYPE_P(end, T_FLOAT)) { 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 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; int64_t mid, org_high;
BSEARCH(int64_as_double_to_num); BSEARCH(int64_as_double_to_num);
} }
#endif #endif
else if (is_integer_p(beg) && is_integer_p(end)) { 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); RETURN_ENUMERATOR(range, 0, 0);
if (EXCL(range)) high = rb_funcall(high, '-', 1, INT2FIX(1)); return bsearch_integer_range(beg, end, EXCL(range));
org_high = high; }
else if (is_integer_p(beg) && NIL_P(end)) {
while (rb_cmpint(rb_funcall(low, id_cmp, 1, high), low, high) < 0) { VALUE diff = LONG2FIX(1);
mid = rb_funcall(rb_funcall(high, '+', 1, low), id_div, 1, INT2FIX(2)); RETURN_ENUMERATOR(range, 0, 0);
while (1) {
VALUE mid = rb_funcall(beg, id_add, 1, diff);
BSEARCH_CHECK(mid); BSEARCH_CHECK(mid);
if (smaller) { if (smaller) {
high = mid; return bsearch_integer_range(beg, mid, 0);
} }
else { diff = rb_funcall(diff, id_mul, 1, LONG2FIX(2));
low = rb_funcall(mid, '+', 1, INT2FIX(1));
} }
} }
if (rb_equal(low, org_high)) {
BSEARCH_CHECK(low);
if (!smaller) return Qnil;
}
return satisfied;
}
else { else {
rb_raise(rb_eTypeError, "can't do binary search for %s", rb_obj_classname(beg)); 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_end = rb_intern("end");
id_excl = rb_intern("excl"); id_excl = rb_intern("excl");
id_integer_p = rb_intern("integer?"); id_integer_p = rb_intern("integer?");
id_add = rb_intern("+");
id_mul = rb_intern("*");
id_div = rb_intern("div"); id_div = rb_intern("div");
rb_cRange = rb_struct_define_without_accessor( rb_cRange = rb_struct_define_without_accessor(

View file

@ -581,6 +581,8 @@ class TestRange < Test::Unit::TestCase
ary = [0, 100, 100, 100, 200] ary = [0, 100, 100, 100, 200]
assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 }) assert_equal(1, (0...ary.size).bsearch {|i| ary[i] >= 100 })
assert_equal(1_000_001, (0...).bsearch {|i| i > 1_000_000 })
end end
def test_bsearch_for_float 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(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_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 end
def check_bsearch_values(range, search, a) 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(nil, (bignum...bignum+ary.size).bsearch {|i| ary[i - bignum] >= 100 })
assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true }) assert_equal(bignum + 0, (bignum...bignum+ary.size).bsearch {|i| true })
assert_equal(nil, (bignum...bignum+ary.size).bsearch {|i| false }) 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 {} } assert_raise(TypeError) { ("a".."z").bsearch {} }
end end