From: Akinori MUSHA Date: 2010-09-10T10:51:07+09:00 Subject: [ruby-dev:42222] Re: Enhancing Numeric#step --pgp-sign-Multipart_Fri_Sep_10_10:51:06_2010-1 Content-Type: text/plain; charset=ISO-2022-JP At Fri, 10 Sep 2010 10:24:12 +0900, I wrote: >  パッチを添付します。':'を末尾('&'を置く場合はその直前)に置く > 形に改めました。いかがでしょうか。  Numeric#step の拡張パッチも更新しました。無限ループの最適化も 問題があったので直しました。 -- Akinori MUSHA / http://akinori.org/ diff --git a/ChangeLog b/ChangeLog index 5e9b53d..1a90dca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +Fri Sep 10 10:46:39 2010 Akinori MUSHA + + * numeric.c (num_step): Introduce keyword arguments (by: and to:), + allowing user to generate an infinite sequence of numbers + without a pain. + + * numeric.c (num_step): Make use of rb_scan_args() to extract an + option hash. + + * numeric.c (num_step): Optimize the infinite loop cases. + Wed Sep 8 06:25:41 2010 Tanaka Akira * ext/pathname/pathname.c (path_setuid_p): Pathname#setuid? translated diff --git a/NEWS b/NEWS index 53b904b..f528410 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,11 @@ with all sufficient information, see the ChangeLog file. * File::NULL name of NULL device. + * Numeric + * Numeric#step now takes keyword arguments :to and :by, and the + limit is optional. An infinite sequence of numbers is generated + when no limit is specified. + * String * new methods: * String#prepend diff --git a/numeric.c b/numeric.c index 25c9e73..c53b537 100644 --- a/numeric.c +++ b/numeric.c @@ -107,6 +107,8 @@ VALUE rb_cFixnum; VALUE rb_eZeroDivError; VALUE rb_eFloatDomainError; +static VALUE sym_to, sym_by; + void rb_num_zerodiv(void) { @@ -1605,18 +1607,26 @@ ruby_float_step(VALUE from, VALUE to, VALUE step, int excl) /* * call-seq: - * num.step(limit[, step]) {|i| block } -> self - * num.step(limit[, step]) -> an_enumerator + * num.step(by: step, to: limit]) {|i| block } -> self + * num.step(by: step, to: limit]) -> an_enumerator + * num.step(limit, step=1) {|i| block } -> self + * num.step(limit, step=1) -> an_enumerator * * Invokes block with the sequence of numbers starting at * num, incremented by step (default 1) on each * call. The loop finishes when the value to be passed to the block * is greater than limit (if step is positive) or less - * than limit (if step is negative). If all the - * arguments are integers, the loop operates using an integer - * counter. If any of the arguments are floating point numbers, all - * are converted to floats, and the loop is executed floor(n + - * n*epsilon)+ 1 times, where n = (limit - + * than limit (if step is negative), where limit + * is defaulted to infinity. In the keyword argument style + * (recommended), any of step and limit (default + * infinity) can be omitted. In the fixed position argument style, + * integer zero as a step (i.e. num.step(limit, 0)) is not allowed + * for historical reasons. + * + * If all the arguments are integers, the loop operates using an + * integer counter. If any of the arguments are floating point + * numbers, all are converted to floats, and the loop is executed + * floor(n + n*epsilon)+ 1 times, where n = (limit - * num)/step. Otherwise, the loop starts at num, uses * either the < or > operator to compare * the counter against limit, and increments itself using the @@ -1624,11 +1634,17 @@ ruby_float_step(VALUE from, VALUE to, VALUE step, int excl) * * If no block is given, an enumerator is returned instead. * + * p 1.step.take(4) + * p 10.step(by: -1).take(4) + * 3.step(to: 5) { |i| print i, " " } * 1.step(10, 2) { |i| print i, " " } - * Math::E.step(Math::PI, 0.2) { |f| print f, " " } + * Math::E.step(to: Math::PI, by: 0.2) { |f| print f, " " } * * produces: * + * [1, 2, 3, 4] + * [10, 9, 8, 7] + * 3 4 5 * 1 3 5 7 9 * 2.71828182845905 2.91828182845905 3.11828182845905 */ @@ -1636,60 +1652,65 @@ ruby_float_step(VALUE from, VALUE to, VALUE step, int excl) static VALUE num_step(int argc, VALUE *argv, VALUE from) { - VALUE to, step; + VALUE to, step, hash; + int desc, inf = 0; RETURN_ENUMERATOR(from, argc, argv); - if (argc == 1) { - to = argv[0]; - step = INT2FIX(1); + + argc = rb_scan_args(argc, argv, "02:", &to, &step, &hash); + + if (!NIL_P(hash)) { + step = rb_hash_aref(hash, sym_by); + to = rb_hash_aref(hash, sym_to); } else { - if (argc == 2) { - to = argv[0]; - step = argv[1]; - } - else { - rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..2)", argc); - } + /* compatibility */ if (rb_equal(step, INT2FIX(0))) { rb_raise(rb_eArgError, "step can't be 0"); } } + if (NIL_P(step)) step = INT2FIX(1); + desc = FIXNUM_P(step) ? FIX2LONG(step) < 0 : RTEST(rb_funcall(step, '<', 1, INT2FIX(0))); + if (NIL_P(to)) to = desc ? DBL2NUM(-INFINITY) : DBL2NUM(INFINITY); + if (TYPE(to) == T_FLOAT) { + double f = RFLOAT_VALUE(to); - if (FIXNUM_P(from) && FIXNUM_P(to) && FIXNUM_P(step)) { - long i, end, diff; + inf = isinf(f) && (signbit(f) ? desc : !desc); + } - i = FIX2LONG(from); - end = FIX2LONG(to); - diff = FIX2LONG(step); + if (FIXNUM_P(from) && (inf || FIXNUM_P(to)) && FIXNUM_P(step)) { + long i = FIX2LONG(from); + long diff = FIX2LONG(step); - if (diff > 0) { - while (i <= end) { + if (inf) { + for (;; i += diff) rb_yield(LONG2FIX(i)); - i += diff; - } } else { - while (i >= end) { - rb_yield(LONG2FIX(i)); - i += diff; + long end = FIX2LONG(to); + + if (desc) { + for (; i >= end; i += diff) + rb_yield(LONG2FIX(i)); + } + else { + for (; i <= end; i += diff) + rb_yield(LONG2FIX(i)); } } } else if (!ruby_float_step(from, to, step, FALSE)) { VALUE i = from; - ID cmp; - if (RTEST(rb_funcall(step, '>', 1, INT2FIX(0)))) { - cmp = '>'; + if (inf) { + for (;; i = rb_funcall(i, '+', 1, step)) + rb_yield(i); } else { - cmp = '<'; - } - for (;;) { - if (RTEST(rb_funcall(i, cmp, 1, to))) break; - rb_yield(i); - i = rb_funcall(i, '+', 1, step); + ID cmp = desc ? '<' : '>'; + + for (; !RTEST(rb_funcall(i, cmp, 1, to)); i = rb_funcall(i, '+', 1, step)) + rb_yield(i); } } return from; @@ -3491,4 +3512,7 @@ Init_Numeric(void) rb_define_method(rb_cFloat, "nan?", flo_is_nan_p, 0); rb_define_method(rb_cFloat, "infinite?", flo_is_infinite_p, 0); rb_define_method(rb_cFloat, "finite?", flo_is_finite_p, 0); + + sym_to = ID2SYM(rb_intern("to")); + sym_by = ID2SYM(rb_intern("by")); } diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index f4fbea4..5525634 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -181,27 +181,58 @@ class TestNumeric < Test::Unit::TestCase assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) a = [] + 1.step(to: 10) {|x| a << x } + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a) + + a = [] 1.step(10, 2) {|x| a << x } assert_equal([1, 3, 5, 7, 9], a) + a = [] + 1.step(to: 10, by: 2) {|x| a << x } + assert_equal([1, 3, 5, 7, 9], a) + assert_raise(ArgumentError) { 1.step(10, 1, 0) { } } assert_raise(ArgumentError) { 1.step(10, 0) { } } + assert_nothing_raised { 1.step(by: 0) } a = [] 10.step(1, -2) {|x| a << x } assert_equal([10, 8, 6, 4, 2], a) a = [] + 10.step(to: 1, by: -2) {|x| a << x } + assert_equal([10, 8, 6, 4, 2], a) + + a = [] 1.0.step(10.0, 2.0) {|x| a << x } assert_equal([1.0, 3.0, 5.0, 7.0, 9.0], a) a = [] + 1.0.step(to: 10.0, by: 2.0) {|x| a << x } + assert_equal([1.0, 3.0, 5.0, 7.0, 9.0], a) + + a = [] 1.step(10, 2**32) {|x| a << x } assert_equal([1], a) a = [] + 1.step(to: 10, by: 2**32) {|x| a << x } + assert_equal([1], a) + + a = [] 10.step(1, -(2**32)) {|x| a << x } assert_equal([10], a) + + a = [] + 10.step(to: 1, by: -(2**32)) {|x| a << x } + assert_equal([10], a) + + a = 10.step.take(4) + assert_equal([10, 11, 12, 13], a) + + a = 10.step(by: -1).take(4) + assert_equal([10, 9, 8, 7], a) end def test_num2long --pgp-sign-Multipart_Fri_Sep_10_10:51:06_2010-1 Content-Type: application/pgp-signature Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.10 (FreeBSD) iEYEABECAAYFAkyJjwoACgkQkgvvx5/Z4e7JyACguDOlS797y9xaA7EZ9WK+Fb/N KJIAnRAgEt1+y1MAIy0MZmFHVBwSogzl =CbBy -----END PGP SIGNATURE----- --pgp-sign-Multipart_Fri_Sep_10_10:51:06_2010-1--