[ruby-dev:42222] Re: Enhancing Numeric#step
From:
"Akinori MUSHA" <knu@...>
Date:
2010-09-10 01:51:07 UTC
List:
ruby-dev #42222
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 <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.
+
+ * 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 <akr@fsij.org>
* 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 <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,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