ruby-changes:30702
From: knu <ko1@a...>
Date: Mon, 2 Sep 2013 23:56:22 +0900 (JST)
Subject: [ruby-changes:30702] knu:r42781 (trunk): Enhance Numeric#step.
knu 2013-09-02 23:56:06 +0900 (Mon, 02 Sep 2013) New Revision: 42781 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=42781 Log: Enhance Numeric#step. * numeric.c (num_step): Default the limit argument to infinity and allow it to be omitted. Keyword arguments (by: and to:) are introduced for ease of use. [Feature #8838] [ruby-dev:47662] [ruby-dev:42194] * numeric.c (num_step): Optimize for infinite loop. Modified files: trunk/ChangeLog trunk/NEWS trunk/numeric.c trunk/test/ruby/test_numeric.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 42780) +++ ChangeLog (revision 42781) @@ -1,3 +1,12 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Mon Sep 2 23:46:29 2013 Akinori MUSHA <knu@i...> + + * numeric.c (num_step): Default the limit argument to infinity and + allow it to be omitted. Keyword arguments (by: and to:) are + introduced for ease of use. [Feature #8838] [ruby-dev:47662] + [ruby-dev:42194] + + * numeric.c (num_step): Optimize for infinite loop. + Mon Sep 2 23:46:10 2013 Nobuyoshi Nakada <nobu@r...> * parse.y (parser_str_options): use valid suffix word only, as well as Index: NEWS =================================================================== --- NEWS (revision 42780) +++ NEWS (revision 42781) @@ -72,6 +72,12 @@ with all sufficient information, see the https://github.com/ruby/ruby/blob/trunk/NEWS#L72 * misc * Mutex#owned? is no longer experimental. +* Numeric + * extended methods: + * Numeric#step allows the limit argument to be omitted, in which + case an infinite sequence of numbers is generated. Keyword + arguments `to` and `by` are introduced for ease of use. + * Process * New methods: * alternative methods to $0/$0=: Index: numeric.c =================================================================== --- numeric.c (revision 42780) +++ numeric.c (revision 42781) @@ -115,6 +115,8 @@ VALUE rb_cFixnum; https://github.com/ruby/ruby/blob/trunk/numeric.c#L115 VALUE rb_eZeroDivError; VALUE rb_eFloatDomainError; +static VALUE sym_to, sym_by; + void rb_num_zerodiv(void) { @@ -1844,24 +1846,59 @@ ruby_num_interval_step_size(VALUE from, https://github.com/ruby/ruby/blob/trunk/numeric.c#L1846 } } +#define NUM_STEP_SCAN_ARGS(argc, argv, to, step, hash, desc, inf) do { \ + 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 { \ + /* compatibility */ \ + if (rb_equal(step, INT2FIX(0))) { \ + rb_raise(rb_eArgError, "step can't be 0"); \ + } \ + } \ + if (NIL_P(step)) step = INT2FIX(1); \ + desc = negative_int_p(step); \ + if (NIL_P(to)) to = desc ? DBL2NUM(-INFINITY) : DBL2NUM(INFINITY); \ + if (TYPE(to) == T_FLOAT) { \ + double f = RFLOAT_VALUE(to); \ + inf = isinf(f) && (signbit(f) ? desc : !desc); \ + } \ + else inf = 0; \ +} while (0) + static VALUE num_step_size(VALUE from, VALUE args, VALUE eobj) { - VALUE to = RARRAY_AREF(args, 0); - VALUE step = (RARRAY_LEN(args) > 1) ? RARRAY_AREF(args, 1) : INT2FIX(1); + VALUE to, step, hash; + int desc, inf; + int argc = args ? RARRAY_LENINT(args) : 0; + VALUE *argv = args ? RARRAY_PTR(args) : 0; + + NUM_STEP_SCAN_ARGS(argc, argv, to, step, hash, desc, inf); + return ruby_num_interval_step_size(from, to, step, FALSE); } /* * 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=nil, step=1) {|i| block } -> self + * num.step(limit=nil, step=1) -> an_enumerator * * Invokes the given block with the sequence of numbers starting at +num+, * incremented by +step+ (defaulted to +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). + * negative), where <i>limit</i> is defaulted to infinity. + * + * In the recommended keyword argument style, either or both 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 + * compatibility reasons. * * If all the arguments are integers, the loop operates using an integer * counter. @@ -1882,11 +1919,17 @@ num_step_size(VALUE from, VALUE args, VA https://github.com/ruby/ruby/blob/trunk/numeric.c#L1919 * * For example: * + * 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, " " } * * Will produce: * + * [1, 2, 3, 4] + * [10, 9, 8, 7] + * 3 4 5 * 1 3 5 7 9 * 2.71828182845905 2.91828182845905 3.11828182845905 */ @@ -1894,56 +1937,46 @@ num_step_size(VALUE from, VALUE args, VA https://github.com/ruby/ruby/blob/trunk/numeric.c#L1937 static VALUE num_step(int argc, VALUE *argv, VALUE from) { - VALUE to, step; + VALUE to, step, hash; + int desc, inf; RETURN_SIZED_ENUMERATOR(from, argc, argv, num_step_size); - if (argc == 1) { - to = argv[0]; - step = INT2FIX(1); - } - else { - rb_check_arity(argc, 1, 2); - to = argv[0]; - step = argv[1]; - if (rb_equal(step, INT2FIX(0))) { - rb_raise(rb_eArgError, "step can't be 0"); - } - } - if (FIXNUM_P(from) && FIXNUM_P(to) && FIXNUM_P(step)) { - long i, end, diff; + NUM_STEP_SCAN_ARGS(argc, argv, to, step, hash, desc, inf); - 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 (positive_int_p(step)) { - 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; @@ -4041,4 +4074,7 @@ Init_Numeric(void) https://github.com/ruby/ruby/blob/trunk/numeric.c#L4074 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")); } Index: test/ruby/test_numeric.rb =================================================================== --- test/ruby/test_numeric.rb (revision 42780) +++ test/ruby/test_numeric.rb (revision 42781) @@ -193,41 +193,64 @@ class TestNumeric < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_numeric.rb#L193 end end - def test_step - a = [] - 1.step(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) + def assert_step(expected, (from, *args), inf: false) + enum = from.step(*args) + size = enum.size + xsize = expected.size + + if inf + assert_send [size, :infinite?], "step size: +infinity" + assert_send [size, :>, 0], "step size: +infinity" + + a = [] + from.step(*args) { |x| a << x; break if a.size == xsize } + assert_equal expected, a, "step" + + a = [] + enum.each { |x| a << x; break if a.size == xsize } + assert_equal expected, a, "step enumerator" + else + assert_equal expected.size, size, "step size" + + a = [] + from.step(*args) { |x| a << x } + assert_equal expected, a, "step" + + a = [] + enum.each { |x| a << x } + assert_equal expected, a, "step enumerator" + end + end + def test_step assert_raise(ArgumentError) { 1.step(10, 1, 0) { } } + assert_raise(ArgumentError) { 1.step(10, 1, 0).size } assert_raise(ArgumentError) { 1.step(10, 0) { } } + assert_raise(ArgumentError) { 1.step(10, 0).size } + assert_nothing_raised { 1.step(by: 0) } + assert_nothing_raised { 1.step(by: 0).size } + + assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 10] + assert_step [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, to: 10] + assert_step [1, 3, 5, 7, 9], [1, 10, 2] + assert_step [1, 3, 5, 7, 9], [1, to: 10, by: 2] + + assert_step [10, 8, 6, 4, 2], [10, 1, -2] + assert_step [10, 8, 6, 4, 2], [10, to: 1, by: -2] + assert_step [1.0, 3.0, 5.0, 7.0, 9.0], [1.0, 10.0, 2.0] + assert_step [1.0, 3.0, 5.0, 7.0, 9.0], [1.0, to: 10.0, by: 2.0] + assert_step [1], [1, 10, 2**32] + assert_step [1], [1, to: 10, by: 2**32] + + assert_step [3, 3, 3, 3], [3, by: 0], inf: true + assert_step [10], [10, 1, -(2**32)] + + assert_step [], [1, 0, Float::INFINITY] + assert_step [], [0, 1, -Float::INFINITY] + assert_step [10], [10, to: 1, by: -(2**32)] - a = [] - 10.step(1, -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.step(10, 2**32) {|x| a << x } - assert_equal([1], a) - - a = [] - 10.step(1, -(2**32)) {|x| a << x } - assert_equal([10], a) - - a = [] - 1.step(0, Float::INFINITY) {|x| a << x } - assert_equal([], a) - - a = [] - 0.step(1, -Float::INFINITY) {|x| a << x } - assert_equal([], a) + assert_step [10, 11, 12, 13], [10], inf: true + assert_step [10, 9, 8, 7], [10, by: -1], inf: true end def test_num2long -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/