[#42194] Enhancing Numeric#step — "Akinori MUSHA" <knu@...>

 Numeric#step の仕様の拡張を提案します。

26 messages 2010/09/08
[#42196] Re: Enhancing Numeric#step — Yukihiro Matsumoto <matz@...> 2010/09/08

まつもと ゆきひろです

[#42200] Re: Enhancing Numeric#step — "Akinori MUSHA" <knu@...> 2010/09/08

At Wed, 8 Sep 2010 22:46:57 +0900,

[#42204] Re: Enhancing Numeric#step — Yukihiro Matsumoto <matz@...> 2010/09/09

まつもと ゆきひろです

[#42232] 1.9.2 readline can't handle cursorkeys, mbcs chars etc (msvcrt) — arton <artonx@...>

artonです。

11 messages 2010/09/10

[#42269] [Ruby 1.9-Bug#3836] Kernel.system, spawnがスペースを含むパスで動作しない — Hiroki Najima <redmine@...>

チケット #3836 が更新されました。 (by Hiroki Najima)

12 messages 2010/09/16
[#42270] WindowsでのKernel.systemの挙動、一貫性について — NAJIMA Hiroki <h.najima@...> 2010/09/16

名島(Nazy)と申します。

[#42310] ビジースレッドがあるとコンテキストスイッチが起きづらくなる — kuwamoto shintaro <beuniv@...>

こんにちは。

9 messages 2010/09/29
[#42315] [bug:trunk] ビジースレッドがあるとコンテキストスイッチが起きづらくなる — "U.Nakamura" <usa@...> 2010/09/30

こんにちは、なかむら(う)です。

[ruby-dev:42194] Enhancing Numeric#step

From: "Akinori MUSHA" <knu@...>
Date: 2010-09-08 12:58:11 UTC
List: ruby-dev #42194
 Numeric#step の仕様の拡張を提案します。

 現在、 Numeric#step は limit を必須引数としているため、手軽に
無限数列を生成することができません。Float::INFINITY ないし 1/0.0
のような値を渡せば可能ではありますが、「1から上限なしでカウント
アップする」のようなよくある要件を満たす方法としては冗長です。

 そこで、上限(下限)なしでループするように limit も省略可能とし、
なおかつ増分のみの指定もできるように疑似キーワード引数を導入して
みました。

1.step {|i| ... }               # i = 1, 2, 3, ...
-1.step(by:-1) {|i| ... }      # i = -1, -2, -3, ...
1.0.step(by: 0.1, to: 2.0).to_a # [1.0, 1.1, ..., 2.0] (余談:誤差に注意)
2.step(by:2).take(100)          # [2, 4, 6, ..., 200]


 キーワードを by: と to: にしたので、従来のように順序で意味を
表すより読みやすいと思います。いかがでしょうか。


commit 5835d772b01da4f048834ec33b2ca4399f0051d6
Author: Akinori MUSHA <knu@idaemons.org>
Date:   Wed Sep 8 21:11:27 2010 +0900

    Enhance Numeric#step.

diff --git a/ChangeLog b/ChangeLog
index 5e9b53d..6227f68 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+Wed Sep  8 21:04:21 2010  Akinori MUSHA  <knu@iDaemons.org>
+
+	* numeric.c (num_step): Introduce keyword arguments (by: and to:),
+	  allowing user to generate an infinite sequence of numbers
+	  without a pain.
+
 Wed Sep  8 06:25:41 2010  Tanaka Akira  <akr@fsij.org>

 	* ext/pathname/pathname.c (path_setuid_p): Pathname#setuid? translated
diff --git a/numeric.c b/numeric.c
index 25c9e73..98b03c3 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 <em>block</em> with the sequence of numbers starting at
  *  <i>num</i>, incremented by <i>step</i> (default 1) on each
  *  call. The loop finishes when the value to be passed to the block
  *  is greater than <i>limit</i> (if <i>step</i> is positive) or less
- *  than <i>limit</i> (if <i>step</i> 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 <i>floor(n +
- *  n*epsilon)+ 1</i> times, where <i>n = (limit -
+ *  than <i>limit</i> (if <i>step</i> is negative), where <i>limit</i>
+ *  is defaulted to infinity. In the keyword argument style
+ *  (recommended), any of <i>step</i> and <i>limit</i> (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
+ *  <i>floor(n + n*epsilon)+ 1</i> times, where <i>n = (limit -
  *  num)/step</i>. Otherwise, the loop starts at <i>num</i>, uses
  *  either the <code><</code> or <code>></code> operator to compare
  *  the counter against <i>limit</i>, 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, " " }
  *
  *  <em>produces:</em>
  *
+ *     [1, 2, 3, 4]
+ *     [10, 9, 8, 7]
+ *     3 4 5
  *     1 3 5 7 9
  *     2.71828182845905 2.91828182845905 3.11828182845905
  */
@@ -1636,43 +1652,75 @@ 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 inf = 0;

     RETURN_ENUMERATOR(from, argc, argv);
-    if (argc == 1) {
+    switch (argc) {
+      case 0:
+	to = DBL2NUM(INFINITY);
+	step = INT2FIX(1);
+	break;
+      case 1:
 	to = argv[0];
 	step = INT2FIX(1);
-    }
-    else {
-	if (argc == 2) {
-	    to = argv[0];
-	    step = argv[1];
-	}
-	else {
-	    rb_raise(rb_eArgError, "wrong number of arguments (%d for 1..2)", argc);
+	switch (TYPE(to)) {
+	  case T_FIXNUM:
+	  case T_BIGNUM:
+	  case T_FLOAT:
+	  case T_RATIONAL:
+	    break;
+	  default:
+	    hash = rb_check_convert_type(to, T_HASH, "Hash", "to_hash");
+	    if (!NIL_P(hash)) {
+		step = rb_hash_aref(hash, sym_by);
+		if (NIL_P(step)) step = INT2FIX(1);
+		to = rb_hash_aref(hash, sym_to);
+		if (NIL_P(to)) to = DBL2NUM(INFINITY);
+	    }
 	}
+	break;
+      case 2:
+	to = argv[0];
+	step = argv[1];
 	if (rb_equal(step, INT2FIX(0))) {
 	    rb_raise(rb_eArgError, "step can't be 0");
 	}
+	break;
+      default:
+	rb_raise(rb_eArgError, "wrong number of arguments (%d for 0..2)", argc);
     }

-    if (FIXNUM_P(from) && FIXNUM_P(to) && FIXNUM_P(step)) {
+    if (TYPE(to) == T_FLOAT && isinf(RFLOAT_VALUE(to)))
+	inf = 1;
+
+    if (FIXNUM_P(from) && (inf || FIXNUM_P(to)) && FIXNUM_P(step)) {
 	long i, end, diff;

-	i = FIX2LONG(from);
-	end = FIX2LONG(to);
-	diff = FIX2LONG(step);
+	if (inf) {
+	    i = FIX2LONG(from);
+	    diff = FIX2LONG(step);

-	if (diff > 0) {
-	    while (i <= end) {
+	    for (;;) {
 		rb_yield(LONG2FIX(i));
 		i += diff;
 	    }
-	}
-	else {
-	    while (i >= end) {
-		rb_yield(LONG2FIX(i));
-		i += diff;
+	} else {
+	    i = FIX2LONG(from);
+	    end = FIX2LONG(to);
+	    diff = FIX2LONG(step);
+
+	    if (diff > 0) {
+		while (i <= end) {
+		    rb_yield(LONG2FIX(i));
+		    i += diff;
+		}
+	    }
+	    else {
+		while (i >= end) {
+		    rb_yield(LONG2FIX(i));
+		    i += diff;
+		}
 	    }
 	}
     }
@@ -3491,4 +3539,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


--
Akinori MUSHA / http://akinori.org/

In This Thread

Prev Next