[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]