From f15069338debcaab151b589de9bcc32acffa6ca0 Mon Sep 17 00:00:00 2001 From: mrkn Date: Mon, 6 Aug 2018 09:08:28 +0000 Subject: [PATCH] enumerator.c: Introduce Enumerator::ArithmeticSequence This commit introduces new core class Enumerator::ArithmeticSequence. Enumerator::ArithmeticSequence is a subclass of Enumerator, and represents a number generator of an arithmetic sequence. After this commit, Numeric#step and Range#step without blocks returned an ArithmeticSequence object instead of an Enumerator. This class introduces the following incompatibilities: - You can create a zero-step ArithmeticSequence, and its size is not ArgumentError, but Infinity. - You can create a negative-step ArithmeticSequence from a range. [ruby-core:82816] [Feature #13904] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64205 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- enumerator.c | 412 ++++++++++++++++++++++++++ internal.h | 5 + numeric.c | 67 ++++- range.c | 18 +- spec/ruby/core/numeric/shared/step.rb | 6 +- spec/ruby/core/numeric/step_spec.rb | 8 +- spec/ruby/core/range/step_spec.rb | 27 +- test/ruby/test_arithmetic_sequence.rb | 367 +++++++++++++++++++++++ test/ruby/test_enumerator.rb | 2 +- test/ruby/test_numeric.rb | 12 +- test/ruby/test_range.rb | 6 +- 11 files changed, 883 insertions(+), 47 deletions(-) create mode 100644 test/ruby/test_arithmetic_sequence.rb diff --git a/enumerator.c b/enumerator.c index 050a9ce58f..66c126d409 100644 --- a/enumerator.c +++ b/enumerator.c @@ -15,6 +15,10 @@ #include "internal.h" #include "id.h" +#ifdef HAVE_FLOAT_H +#include +#endif + /* * Document-class: Enumerator * @@ -105,6 +109,7 @@ VALUE rb_cEnumerator; static VALUE rb_cLazy; static ID id_rewind, id_new, id_yield, id_to_enum; static ID id_next, id_result, id_lazy, id_receiver, id_arguments, id_memo, id_method, id_force; +static ID id_begin, id_end, id_step, id_exclude_end; static VALUE sym_each, sym_cycle; #define id_call idCall @@ -156,6 +161,8 @@ struct proc_entry { static VALUE generator_allocate(VALUE klass); static VALUE generator_init(VALUE obj, VALUE proc); +static VALUE rb_cArithSeq; + /* * Enumerator */ @@ -2375,6 +2382,391 @@ stop_result(VALUE self) return rb_attr_get(self, id_result); } +VALUE +rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv, + rb_enumerator_size_func *size_fn, + VALUE beg, VALUE end, VALUE step, int excl) +{ + VALUE aseq = enumerator_init(enumerator_allocate(rb_cArithSeq), + obj, meth, argc, argv, size_fn, Qnil); + rb_ivar_set(aseq, id_begin, beg); + rb_ivar_set(aseq, id_end, end); + rb_ivar_set(aseq, id_step, step); + rb_ivar_set(aseq, id_exclude_end, excl ? Qtrue : Qfalse); + return aseq; +} + +static inline VALUE +arith_seq_begin(VALUE self) +{ + return rb_ivar_get(self, id_begin); +} + +static inline VALUE +arith_seq_end(VALUE self) +{ + return rb_ivar_get(self, id_end); +} + +static inline VALUE +arith_seq_step(VALUE self) +{ + return rb_ivar_get(self, id_step); +} + +static inline VALUE +arith_seq_exclude_end(VALUE self) +{ + return rb_ivar_get(self, id_exclude_end); +} + +static inline int +arith_seq_exclude_end_p(VALUE self) +{ + return RTEST(arith_seq_exclude_end(self)); +} + +static VALUE +arith_seq_first(int argc, VALUE *argv, VALUE self) +{ + VALUE b, e, s, len_1; + + b = arith_seq_begin(self); + e = arith_seq_end(self); + s = arith_seq_step(self); + + if (!NIL_P(e)) { + len_1 = rb_int_idiv(rb_int_minus(e, b), s); + if (rb_num_negative_int_p(len_1)) { + if (argc == 0) { + return Qnil; + } + return rb_ary_new_capa(0); + } + } + + if (argc == 0) { + return b; + } + + /* TODO: optimization */ + + return rb_call_super(argc, argv); +} + +static VALUE +arith_seq_last(int argc, VALUE *argv, VALUE self) +{ + VALUE b, e, s, len_1, len, last, nv, ary; + int last_is_adjusted; + long n; + + e = arith_seq_end(self); + if (NIL_P(e)) { + rb_raise(rb_eRangeError, + "cannot get the last element of endless arithmetic sequence"); + } + + b = arith_seq_begin(self); + s = arith_seq_step(self); + + len_1 = rb_int_idiv(rb_int_minus(e, b), s); + if (rb_num_negative_int_p(len_1)) { + if (argc == 0) { + return Qnil; + } + return rb_ary_new_capa(0); + } + + last = rb_int_plus(b, rb_int_mul(s, len_1)); + if ((last_is_adjusted = arith_seq_exclude_end_p(self) && rb_equal(last, e))) { + last = rb_int_minus(last, s); + } + + if (argc == 0) { + return last; + } + + if (last_is_adjusted) { + len = len_1; + } + else { + len = rb_int_plus(len_1, INT2FIX(1)); + } + + rb_scan_args(argc, argv, "1", &nv); + if (RTEST(rb_int_gt(nv, len))) { + nv = len; + } + n = NUM2LONG(nv); + if (n < 0) { + rb_raise(rb_eArgError, "negative array size"); + } + + ary = rb_ary_new_capa(n); + b = rb_int_minus(last, rb_int_mul(s, nv)); + while (n) { + b = rb_int_plus(b, s); + rb_ary_push(ary, b); + --n; + } + + return ary; +} + +static VALUE +arith_seq_inspect(VALUE self) +{ + struct enumerator *e; + VALUE eobj, str, eargs; + int range_p; + + TypedData_Get_Struct(self, struct enumerator, &enumerator_data_type, e); + + eobj = rb_attr_get(self, id_receiver); + if (NIL_P(eobj)) { + eobj = e->obj; + } + + range_p = RTEST(rb_obj_is_kind_of(eobj, rb_cRange)); + str = rb_sprintf("(%s%"PRIsVALUE"%s.", range_p ? "(" : "", eobj, range_p ? ")" : ""); + + rb_str_buf_append(str, rb_id2str(e->meth)); + + eargs = rb_attr_get(eobj, id_arguments); + if (NIL_P(eargs)) { + eargs = e->args; + } + if (eargs != Qfalse) { + long argc = RARRAY_LEN(eargs); + const VALUE *argv = RARRAY_CONST_PTR(eargs); /* WB: no new reference */ + + if (argc > 0) { + VALUE kwds = Qnil; + + rb_str_buf_cat2(str, "("); + + if (RB_TYPE_P(argv[argc-1], T_HASH)) { + int all_key = TRUE; + rb_hash_foreach(argv[argc-1], key_symbol_p, (VALUE)&all_key); + if (all_key) kwds = argv[--argc]; + } + + while (argc--) { + VALUE arg = *argv++; + + rb_str_append(str, rb_inspect(arg)); + rb_str_buf_cat2(str, ", "); + OBJ_INFECT(str, arg); + } + if (!NIL_P(kwds)) { + rb_hash_foreach(kwds, kwd_append, str); + } + rb_str_set_len(str, RSTRING_LEN(str)-2); /* drop the last ", " */ + rb_str_buf_cat2(str, ")"); + } + } + + rb_str_buf_cat2(str, ")"); + + return str; +} + +static VALUE +arith_seq_eq(VALUE self, VALUE other) +{ + if (!RTEST(rb_obj_is_kind_of(other, rb_cArithSeq))) { + return Qfalse; + } + + if (!rb_equal(arith_seq_begin(self), arith_seq_begin(other))) { + return Qfalse; + } + + if (!rb_equal(arith_seq_end(self), arith_seq_end(other))) { + return Qfalse; + } + + if (!rb_equal(arith_seq_step(self), arith_seq_step(other))) { + return Qfalse; + } + + if (arith_seq_exclude_end_p(self) != arith_seq_exclude_end_p(other)) { + return Qfalse; + } + + return Qtrue; +} + +static VALUE +arith_seq_hash(VALUE self) +{ + st_index_t hash; + VALUE v; + + hash = rb_hash_start(arith_seq_exclude_end_p(self)); + v = rb_hash(arith_seq_begin(self)); + hash = rb_hash_uint(hash, NUM2LONG(v)); + v = rb_hash(arith_seq_end(self)); + hash = rb_hash_uint(hash, NUM2LONG(v)); + v = rb_hash(arith_seq_step(self)); + hash = rb_hash_uint(hash, NUM2LONG(v)); + hash = rb_hash_end(hash); + + return LONG2FIX(hash); +} + +struct arith_seq_gen { + VALUE current; + VALUE end; + VALUE step; + int excl; +}; + +static VALUE +arith_seq_each(VALUE self) +{ + VALUE c, e, s, len_1, last; + int x; + + if (!rb_block_given_p()) return self; + + c = arith_seq_begin(self); + e = arith_seq_end(self); + s = arith_seq_step(self); + x = arith_seq_exclude_end_p(self); + + if (ruby_float_step(c, e, s, x, TRUE)) { + return self; + } + + if (NIL_P(e)) { + while (1) { + rb_yield(c); + c = rb_int_plus(c, s); + } + + return self; + } + + if (rb_equal(s, INT2FIX(0))) { + while (1) { + rb_yield(c); + } + + return self; + } + + len_1 = rb_int_idiv(rb_int_minus(e, c), s); + last = rb_int_plus(c, rb_int_mul(s, len_1)); + if (x && rb_equal(last, e)) { + last = rb_int_minus(last, s); + } + + if (rb_num_negative_int_p(s)) { + while (RTEST(rb_int_ge(c, last))) { + rb_yield(c); + c = rb_int_plus(c, s); + } + } + else { + while (RTEST(rb_int_ge(last, c))) { + rb_yield(c); + c = rb_int_plus(c, s); + } + } + + return self; +} + +static double +arith_seq_float_step_size(double beg, double end, double step, int excl) +{ + double const epsilon = DBL_EPSILON; + double n = (end - beg) / step; + double err = (fabs(beg) + fabs(end) + fabs(end - beg)) / fabs(step) * epsilon; + + if (isinf(step)) { + return step > 0 ? beg <= end : beg >= end; + } + if (step == 0) { + return HUGE_VAL; + } + if (err > 0.5) err = 0.5; + if (excl) { + if (n <= 0) return 0; + if (n < 1) + n = 0; + else + n = floor(n - err); + } + else { + if (n < 0) return 0; + n = floor(n + err); + } + return n + 1; +} + +static VALUE +arith_seq_size(VALUE self) +{ + VALUE b, e, s, len_1, len, last; + int x; + + b = arith_seq_begin(self); + e = arith_seq_end(self); + s = arith_seq_step(self); + x = arith_seq_exclude_end_p(self); + + if (RB_FLOAT_TYPE_P(b) || RB_FLOAT_TYPE_P(e) || RB_FLOAT_TYPE_P(s)) { + double ee, n; + + if (NIL_P(e)) { + if (rb_num_negative_int_p(s)) { + ee = -HUGE_VAL; + } + else { + ee = HUGE_VAL; + } + } + else { + ee = NUM2DBL(e); + } + + n = arith_seq_float_step_size(NUM2DBL(b), ee, NUM2DBL(s), x); + if (isinf(n)) return DBL2NUM(n); + if (POSFIXABLE(n)) return LONG2FIX(n); + return rb_dbl2big(n); + } + + if (NIL_P(e)) { + return DBL2NUM(HUGE_VAL); + } + + if (!rb_obj_is_kind_of(s, rb_cNumeric)) { + s = rb_to_int(s); + } + + if (rb_equal(s, INT2FIX(0))) { + return DBL2NUM(HUGE_VAL); + } + + len_1 = rb_int_idiv(rb_int_minus(e, b), s); + if (rb_num_negative_int_p(len_1)) { + return INT2FIX(0); + } + + last = rb_int_plus(b, rb_int_mul(s, len_1)); + if (x && rb_equal(last, e)) { + len = len_1; + } + else { + len = rb_int_plus(len_1, INT2FIX(1)); + } + + return len; +} + void InitVM_Enumerator(void) { @@ -2450,6 +2842,22 @@ InitVM_Enumerator(void) rb_define_method(rb_cYielder, "yield", yielder_yield, -2); rb_define_method(rb_cYielder, "<<", yielder_yield_push, -2); + /* ArithmeticSequence */ + rb_cArithSeq = rb_define_class_under(rb_cEnumerator, "ArithmeticSequence", rb_cEnumerator); + rb_define_method(rb_cArithSeq, "begin", arith_seq_begin, 0); + rb_define_method(rb_cArithSeq, "end", arith_seq_end, 0); + rb_define_method(rb_cArithSeq, "exclude_end?", arith_seq_exclude_end, 0); + rb_define_method(rb_cArithSeq, "step", arith_seq_step, 0); + rb_define_method(rb_cArithSeq, "first", arith_seq_first, -1); + rb_define_method(rb_cArithSeq, "last", arith_seq_last, -1); + rb_define_method(rb_cArithSeq, "inspect", arith_seq_inspect, 0); + rb_define_method(rb_cArithSeq, "==", arith_seq_eq, 1); + rb_define_method(rb_cArithSeq, "===", arith_seq_eq, 1); + rb_define_method(rb_cArithSeq, "eql?", arith_seq_eq, 1); + rb_define_method(rb_cArithSeq, "hash", arith_seq_hash, 0); + rb_define_method(rb_cArithSeq, "each", arith_seq_each, 0); + rb_define_method(rb_cArithSeq, "size", arith_seq_size, 0); + rb_provide("enumerator.so"); /* for backward compatibility */ } @@ -2469,6 +2877,10 @@ Init_Enumerator(void) id_method = rb_intern("method"); id_force = rb_intern("force"); id_to_enum = rb_intern("to_enum"); + id_begin = rb_intern("begin"); + id_end = rb_intern("end"); + id_step = rb_intern("step"); + id_exclude_end = rb_intern("exclude_end"); sym_each = ID2SYM(id_each); sym_cycle = ID2SYM(rb_intern("cycle")); diff --git a/internal.h b/internal.h index 7047eb4c3d..81c556c0be 100644 --- a/internal.h +++ b/internal.h @@ -1995,6 +1995,11 @@ enum rb_int_parse_flags { }; VALUE rb_int_parse_cstr(const char *str, ssize_t len, char **endp, size_t *ndigits, int base, int flags); +/* enumerator.c (export) */ +VALUE rb_arith_seq_new(VALUE obj, VALUE meth, int argc, VALUE const *argv, + rb_enumerator_size_func *size_fn, + VALUE beg, VALUE end, VALUE step, int excl); + /* error.c (export) */ int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data); NORETURN(void rb_unexpected_type(VALUE,int)); diff --git a/numeric.c b/numeric.c index 090b5e57ba..b05b8f36e2 100644 --- a/numeric.c +++ b/numeric.c @@ -2581,10 +2581,9 @@ num_step_negative_p(VALUE num) } static int -num_step_scan_args(int argc, const VALUE *argv, VALUE *to, VALUE *step) +num_step_extract_args(int argc, const VALUE *argv, VALUE *to, VALUE *step, VALUE *by) { VALUE hash; - int desc; argc = rb_scan_args(argc, argv, "02:", to, step, &hash); if (!NIL_P(hash)) { @@ -2599,28 +2598,47 @@ num_step_scan_args(int argc, const VALUE *argv, VALUE *to, VALUE *step) } if (values[1] != Qundef) { if (argc > 1) rb_raise(rb_eArgError, "step is given twice"); - *step = values[1]; + *by = values[1]; } } + + return argc; +} + +static int +num_step_check_fix_args(int argc, VALUE *to, VALUE *step, VALUE by, int fix_nil, int allow_zero_step) +{ + int desc; + if (by != Qundef) { + *step = by; + } else { - /* compatibility */ - if (argc > 1 && NIL_P(*step)) { - rb_raise(rb_eTypeError, "step must be numeric"); - } - if (rb_equal(*step, INT2FIX(0))) { - rb_raise(rb_eArgError, "step can't be 0"); - } + /* compatibility */ + if (argc > 1 && NIL_P(*step)) { + rb_raise(rb_eTypeError, "step must be numeric"); + } + if (!allow_zero_step && rb_equal(*step, INT2FIX(0))) { + rb_raise(rb_eArgError, "step can't be 0"); + } } if (NIL_P(*step)) { *step = INT2FIX(1); } desc = num_step_negative_p(*step); - if (NIL_P(*to)) { - *to = desc ? DBL2NUM(-HUGE_VAL) : DBL2NUM(HUGE_VAL); + if (fix_nil && NIL_P(*to)) { + *to = desc ? DBL2NUM(-HUGE_VAL) : DBL2NUM(HUGE_VAL); } return desc; } +static int +num_step_scan_args(int argc, const VALUE *argv, VALUE *to, VALUE *step, int fix_nil, int allow_zero_step) +{ + VALUE by = Qundef; + argc = num_step_extract_args(argc, argv, to, step, &by); + return num_step_check_fix_args(argc, to, step, by, fix_nil, allow_zero_step); +} + static VALUE num_step_size(VALUE from, VALUE args, VALUE eobj) { @@ -2628,7 +2646,7 @@ num_step_size(VALUE from, VALUE args, VALUE eobj) int argc = args ? RARRAY_LENINT(args) : 0; const VALUE *argv = args ? RARRAY_CONST_PTR(args) : 0; - num_step_scan_args(argc, argv, &to, &step); + num_step_scan_args(argc, argv, &to, &step, TRUE, FALSE); return ruby_num_interval_step_size(from, to, step, FALSE); } @@ -2691,9 +2709,28 @@ num_step(int argc, VALUE *argv, VALUE from) VALUE to, step; int desc, inf; - RETURN_SIZED_ENUMERATOR(from, argc, argv, num_step_size); + /* RETURN_SIZED_ENUMERATOR(from, argc, argv, num_step_size); */ - desc = num_step_scan_args(argc, argv, &to, &step); + if (!rb_block_given_p()) { + VALUE by = Qundef; + + num_step_extract_args(argc, argv, &to, &step, &by); + if (by != Qundef) { + step = by; + } + if (NIL_P(step)) { + step = INT2FIX(1); + } + if ((NIL_P(to) || rb_obj_is_kind_of(to, rb_cNumeric)) && + rb_obj_is_kind_of(step, rb_cNumeric)) { + return rb_arith_seq_new(from, ID2SYM(rb_frame_this_func()), argc, argv, + num_step_size, from, to, step, FALSE); + } + + RETURN_SIZED_ENUMERATOR(from, argc, argv, num_step_size); + } + + desc = num_step_scan_args(argc, argv, &to, &step, TRUE, FALSE); if (rb_equal(step, INT2FIX(0))) { inf = 1; } diff --git a/range.c b/range.c index bcc8d1a8f4..56c1ee253b 100644 --- a/range.c +++ b/range.c @@ -388,18 +388,26 @@ range_step(int argc, VALUE *argv, VALUE range) { VALUE b, e, step, tmp; - RETURN_SIZED_ENUMERATOR(range, argc, argv, range_step_size); - b = RANGE_BEG(range); e = RANGE_END(range); if (argc == 0) { - step = INT2FIX(1); + step = INT2FIX(1); } else { - rb_scan_args(argc, argv, "01", &step); - step = check_step_domain(step); + rb_scan_args(argc, argv, "01", &step); } + if (!rb_block_given_p()) { + if (rb_obj_is_kind_of(b, rb_cNumeric) && (NIL_P(e) || rb_obj_is_kind_of(e, rb_cNumeric))) { + return rb_arith_seq_new(range, ID2SYM(rb_frame_this_func()), argc, argv, + range_step_size, b, e, step, EXCL(range)); + } + + RETURN_SIZED_ENUMERATOR(range, argc, argv, range_step_size); + } + + step = check_step_domain(step); + if (FIXNUM_P(b) && NIL_P(e) && FIXNUM_P(step)) { long i = FIX2LONG(b), unit = FIX2LONG(step); do { diff --git a/spec/ruby/core/numeric/shared/step.rb b/spec/ruby/core/numeric/shared/step.rb index fa703b7554..f058c64a50 100644 --- a/spec/ruby/core/numeric/shared/step.rb +++ b/spec/ruby/core/numeric/shared/step.rb @@ -259,15 +259,15 @@ describe :numeric_step, :shared => true do describe "when no block is given" do it "returns an Enumerator when step is 0" do - 1.send(@method, *@step_args.call(2, 0)).should be_an_instance_of(Enumerator) + 1.send(@method, *@step_args.call(2, 0)).should be_an_instance_of(Enumerator::ArithmeticSequence) end it "returns an Enumerator when not passed a block and self > stop" do - 1.send(@method, *@step_args.call(0, 2)).should be_an_instance_of(Enumerator) + 1.send(@method, *@step_args.call(0, 2)).should be_an_instance_of(Enumerator::ArithmeticSequence) end it "returns an Enumerator when not passed a block and self < stop" do - 1.send(@method, *@step_args.call(2, 3)).should be_an_instance_of(Enumerator) + 1.send(@method, *@step_args.call(2, 3)).should be_an_instance_of(Enumerator::ArithmeticSequence) end it "returns an Enumerator that uses the given step" do diff --git a/spec/ruby/core/numeric/step_spec.rb b/spec/ruby/core/numeric/step_spec.rb index a932b4df73..54133f8810 100644 --- a/spec/ruby/core/numeric/step_spec.rb +++ b/spec/ruby/core/numeric/step_spec.rb @@ -23,23 +23,23 @@ describe "Numeric#step" do describe "when no block is given" do it "returns an Enumerator when step is 0" do - 1.step(5, 0).should be_an_instance_of(Enumerator) + 1.step(5, 0).should be_an_instance_of(Enumerator::ArithmeticSequence) end it "returns an Enumerator when step is 0.0" do - 1.step(2, 0.0).should be_an_instance_of(Enumerator) + 1.step(2, 0.0).should be_an_instance_of(Enumerator::ArithmeticSequence) end describe "returned Enumerator" do describe "size" do it "raises an ArgumentError when step is 0" do enum = 1.step(5, 0) - lambda { enum.size }.should raise_error(ArgumentError) + enum.size.should == Float::INFINITY end it "raises an ArgumentError when step is 0.0" do enum = 1.step(2, 0.0) - lambda { enum.size }.should raise_error(ArgumentError) + enum.size.should == Float::INFINITY end end end diff --git a/spec/ruby/core/range/step_spec.rb b/spec/ruby/core/range/step_spec.rb index 3d5e75b033..c364600fb0 100644 --- a/spec/ruby/core/range/step_spec.rb +++ b/spec/ruby/core/range/step_spec.rb @@ -5,9 +5,9 @@ describe "Range#step" do ScratchPad.record [] end - it "returns an enumerator when no block is given" do + it "returns an arithmetic sequence when no block is given" do enum = (1..10).step(4) - enum.should be_an_instance_of(Enumerator) + enum.should be_an_instance_of(Enumerator::ArithmeticSequence) enum.to_a.should eql([1, 5, 9]) end @@ -263,7 +263,7 @@ describe "Range#step" do end describe "when no block is given" do - describe "returned Enumerator" do + describe "returned Enumerator::ArithmeticSequence" do describe "size" do it "raises a TypeError if step does not respond to #to_int" do obj = mock("Range#step non-integer") @@ -279,19 +279,9 @@ describe "Range#step" do lambda { enum.size }.should raise_error(TypeError) end - it "raises an ArgumentError if step is 0" do - enum = (-1..1).step(0) - lambda { enum.size }.should raise_error(ArgumentError) - end - - it "raises an ArgumentError if step is 0.0" do - enum = (-1..1).step(0.0) - lambda { enum.size }.should raise_error(ArgumentError) - end - - it "raises an ArgumentError if step is negative" do - enum = (-1..1).step(-2) - lambda { enum.size }.should raise_error(ArgumentError) + it "returns Float::INFINITY for zero step" do + (-1..1).step(0).size.should == Float::INFINITY + (-1..1).step(0.0).size.should == Float::INFINITY end it "returns the ceil of range size divided by the number of steps" do @@ -307,6 +297,11 @@ describe "Range#step" do (-5...5).step(2).size.should == 5 end + it "returns the ceil of range size divided by the number of steps even if step is negative" do + (-1..1).step(-1).size.should == 0 + (1..-1).step(-1).size.should == 3 + end + it "returns the correct number of steps when one of the arguments is a float" do (-1..1.0).step(0.5).size.should == 5 (-1.0...1.0).step(0.5).size.should == 4 diff --git a/test/ruby/test_arithmetic_sequence.rb b/test/ruby/test_arithmetic_sequence.rb new file mode 100644 index 0000000000..fe1af362f4 --- /dev/null +++ b/test/ruby/test_arithmetic_sequence.rb @@ -0,0 +1,367 @@ +# frozen_string_literal: false +require 'test/unit' + +class TestArithmeticSequence < Test::Unit::TestCase + def test_begin + assert_equal(1, 1.step.begin) + assert_equal(1, 1.step(10).begin) + assert_equal(1, 1.step(to: 10).begin) + assert_equal(1, 1.step(nil).begin) + assert_equal(1, 1.step(to: nil).begin) + assert_equal(1, 1.step(by: 2).begin) + assert_equal(1, 1.step(by: -1).begin) + assert_equal(1, 1.step(by: nil).begin) + assert_equal(1, 1.step(10, 2).begin) + assert_equal(1, 1.step(10, by: 2).begin) + assert_equal(1, 1.step(to: 10, by: 2).begin) + assert_equal(10, 10.step(to: 1, by: -1).begin) + assert_equal(10, 10.step(to: 1, by: -2).begin) + assert_equal(10, 10.step(to: -1, by: -2).begin) + assert_equal(10.0, 10.0.step(to: -1.0, by: -2.0).begin) + end + + def test_end + assert_equal(nil, 1.step.end) + assert_equal(10, 1.step(10).end) + assert_equal(10, 1.step(to: 10).end) + assert_equal(nil, 1.step(nil).end) + assert_equal(nil, 1.step(to: nil).end) + assert_equal(nil, 1.step(by: 2).end) + assert_equal(nil, 1.step(by: -1).end) + assert_equal(nil, 1.step(by: nil).end) + assert_equal(10, 1.step(10, 2).end) + assert_equal(10, 1.step(10, by: 2).end) + assert_equal(10, 1.step(to: 10, by: 2).end) + assert_equal(1, 10.step(to: 1, by: -1).end) + assert_equal(1, 10.step(to: 1, by: -2).end) + assert_equal(-1, 10.step(to: -1, by: -2).end) + assert_equal(-1.0, 10.0.step(to: -1.0, by: -2.0).end) + end + + def test_exclude_end_p + assert_equal(false, 1.step.exclude_end?) + assert_equal(false, 1.step(10).exclude_end?) + assert_equal(false, 1.step(to: 10).exclude_end?) + assert_equal(false, 1.step(nil).exclude_end?) + assert_equal(false, 1.step(to: nil).exclude_end?) + assert_equal(false, 1.step(by: 2).exclude_end?) + assert_equal(false, 1.step(by: -1).exclude_end?) + assert_equal(false, 1.step(by: nil).exclude_end?) + assert_equal(false, 1.step(10, 2).exclude_end?) + assert_equal(false, 1.step(10, by: 2).exclude_end?) + assert_equal(false, 1.step(to: 10, by: 2).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -1).exclude_end?) + assert_equal(false, 10.step(to: 1, by: -2).exclude_end?) + assert_equal(false, 10.step(to: -1, by: -2).exclude_end?) + end + + def test_step + assert_equal(1, 1.step.step) + assert_equal(1, 1.step(10).step) + assert_equal(1, 1.step(to: 10).step) + assert_equal(1, 1.step(nil).step) + assert_equal(1, 1.step(to: nil).step) + assert_equal(2, 1.step(by: 2).step) + assert_equal(-1, 1.step(by: -1).step) + assert_equal(1, 1.step(by: nil).step) + assert_equal(2, 1.step(10, 2).step) + assert_equal(2, 1.step(10, by: 2).step) + assert_equal(2, 1.step(to: 10, by: 2).step) + assert_equal(-1, 10.step(to: 1, by: -1).step) + assert_equal(-2, 10.step(to: 1, by: -2).step) + assert_equal(-2, 10.step(to: -1, by: -2).step) + assert_equal(-2.0, 10.0.step(to: -1.0, by: -2.0).step) + end + + def test_eq + seq = 1.step + assert_equal(seq, seq) + assert_equal(seq, 1.step) + assert_equal(seq, 1.step(nil)) + end + + def test_eqq + seq = 1.step + assert_operator(seq, :===, seq) + assert_operator(seq, :===, 1.step) + assert_operator(seq, :===, 1.step(nil)) + end + + def test_eql_p + seq = 1.step + assert_operator(seq, :eql?, seq) + assert_operator(seq, :eql?, 1.step) + assert_operator(seq, :eql?, 1.step(nil)) + end + + def test_hash + seq = 1.step + assert_equal(seq.hash, seq.hash) + assert_equal(seq.hash, 1.step.hash) + assert_equal(seq.hash, 1.step(nil).hash) + end + + def test_first + seq = 1.step + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 2, 3], seq.first(3)) + + seq = 1.step(by: 2) + assert_equal(1, seq.first) + assert_equal([1], seq.first(1)) + assert_equal([1, 3, 5], seq.first(3)) + + seq = 10.step(by: -2) + assert_equal(10, seq.first) + assert_equal([10], seq.first(1)) + assert_equal([10, 8, 6], seq.first(3)) + + seq = 1.step(by: 4) + assert_equal([1, 5, 9], seq.first(3)) + + seq = 1.step(10, by: 4) + assert_equal([1, 5, 9], seq.first(5)) + + seq = 1.step(0) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], seq.first(3)) + + seq = 1.step(10, by: -1) + assert_equal(nil, seq.first) + assert_equal([], seq.first(1)) + assert_equal([], seq.first(3)) + + seq = 10.0.step(-1.0, by: -2.0) + assert_equal(10.0, seq.first) + assert_equal([10.0], seq.first(1)) + assert_equal([10.0, 8.0, 6.0], seq.first(3)) + end + + def test_last + seq = 1.step(10) + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = 1.step(10, 2) + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([5, 7, 9], seq.last(3)) + + seq = 10.step(1, -2) + assert_equal(2, seq.last) + assert_equal([2], seq.last(1)) + assert_equal([6, 4, 2], seq.last(3)) + + seq = 10.step(-1, -2) + assert_equal(0, seq.last) + + seq = 1.step(10, 4) + assert_equal([1, 5, 9], seq.last(5)) + + seq = 10.step(1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = 1.step(10, -1) + assert_equal(nil, seq.last) + assert_equal([], seq.last(1)) + assert_equal([], seq.last(5)) + + seq = (1..10).step + assert_equal(10, seq.last) + assert_equal([10], seq.last(1)) + assert_equal([8, 9, 10], seq.last(3)) + + seq = (1...10).step + assert_equal(9, seq.last) + assert_equal([9], seq.last(1)) + assert_equal([7, 8, 9], seq.last(3)) + + seq = 10.0.step(-3.0, by: -2.0) + assert_equal(-2.0, seq.last) + assert_equal([-2.0], seq.last(1)) + assert_equal([2.0, 0.0, -2.0], seq.last(3)) + end + + def test_to_a + assert_equal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 1.step(10).to_a) + assert_equal([1, 3, 5, 7, 9], 1.step(10, 2).to_a) + assert_equal([1, 3, 5, 7, 9], (1..10).step(2).to_a) + assert_equal([10, 8, 6, 4, 2], 10.step(1, by: -2).to_a) + assert_equal([10, 8, 6, 4, 2], (10..1).step(-2).to_a) + assert_equal([10.0, 8.0, 6.0, 4.0, 2.0], (10.0..1.0).step(-2.0).to_a) + end + + def test_slice + seq = 1.step(10, 2) + assert_equal([[1, 3, 5], [7, 9]], seq.each_slice(3).to_a) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [4, 2]], seq.each_slice(3).to_a) + end + + def test_cons + seq = 1.step(10, 2) + assert_equal([[1, 3, 5], [3, 5, 7], [5, 7, 9]], seq.each_cons(3).to_a) + + seq = 10.step(1, -2) + assert_equal([[10, 8, 6], [8, 6, 4], [6, 4, 2]], seq.each_cons(3).to_a) + end + + def test_with_index + seq = 1.step(6, 2) + assert_equal([[1, 0], [3, 1], [5, 2]], seq.with_index.to_a) + assert_equal([[1, 10], [3, 11], [5, 12]], seq.with_index(10).to_a) + + seq = 10.step(5, -2) + assert_equal([[10, 0], [8, 1], [6, 2]], seq.with_index.to_a) + assert_equal([[10, 10], [8, 11], [6, 12]], seq.with_index(10).to_a) + end + + def test_with_object + obj = [0, 1] + seq = 1.step(10, 2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([25, 945], ret) + + obj = [0, 1] + seq = 10.step(1, -2) + ret = seq.each_with_object(obj) do |i, memo| + memo[0] += i + memo[1] *= i + end + assert_same(obj, ret) + assert_equal([30, 3840], ret) + end + + def test_next + seq = 1.step(10, 2) + [1, 3, 5, 7, 9].each do |i| + assert_equal(i, seq.next) + end + + seq = 10.step(1, -2) + [10, 8, 6, 4, 2].each do |i| + assert_equal(i, seq.next) + end + end + + def test_next_rewind + seq = 1.step(6, 2) + assert_equal(1, seq.next) + assert_equal(3, seq.next) + seq.rewind + assert_equal(1, seq.next) + assert_equal(3, seq.next) + assert_equal(5, seq.next) + assert_raise(StopIteration) { seq.next } + + seq = 10.step(5, -2) + assert_equal(10, seq.next) + assert_equal(8, seq.next) + seq.rewind + assert_equal(10, seq.next) + assert_equal(8, seq.next) + assert_equal(6, seq.next) + assert_raise(StopIteration) { seq.next } + end + + def test_next_after_stopiteration + seq = 1.step(2, 2) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + seq.rewind + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.next } + assert_raise(StopIteration) { seq.next } + end + + def test_stop_result + seq = 1.step(2, 2) + res = seq.each {} + assert_equal(1, seq.next) + exc = assert_raise(StopIteration) { seq.next } + assert_equal(res, exc.result) + end + + def test_peek + seq = 1.step(2, 2) + assert_equal(1, seq.peek) + assert_equal(1, seq.peek) + assert_equal(1, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + + seq = 10.step(9, -2) + assert_equal(10, seq.peek) + assert_equal(10, seq.peek) + assert_equal(10, seq.next) + assert_raise(StopIteration) { seq.peek } + assert_raise(StopIteration) { seq.peek } + end + + def test_next_values + seq = 1.step(2, 2) + assert_equal([1], seq.next_values) + end + + def test_peek_values + seq = 1.step(2, 2) + assert_equal([1], seq.peek_values) + end + + def test_num_step_inspect + assert_equal('(1.step)', 1.step.inspect) + assert_equal('(1.step(10))', 1.step(10).inspect) + assert_equal('(1.step(10, 2))', 1.step(10, 2).inspect) + assert_equal('(1.step(10, by: 2))', 1.step(10, by: 2).inspect) + assert_equal('(1.step(by: 2))', 1.step(by: 2).inspect) + end + + def test_range_step_inspect + assert_equal('((1..).step)', (1..).step.inspect) + assert_equal('((1..10).step)', (1..10).step.inspect) + assert_equal('((1..10).step(2))', (1..10).step(2).inspect) + end + + def test_num_step_size + assert_equal(10, 1.step(10).size) + assert_equal(5, 1.step(10, 2).size) + assert_equal(4, 1.step(10, 3).size) + assert_equal(1, 1.step(10, 10).size) + assert_equal(0, 1.step(0).size) + assert_equal(Float::INFINITY, 1.step.size) + + assert_equal(10, 10.step(1, -1).size) + assert_equal(5, 10.step(1, -2).size) + assert_equal(4, 10.step(1, -3).size) + assert_equal(1, 10.step(1, -10).size) + assert_equal(0, 1.step(2, -1).size) + assert_equal(Float::INFINITY, 1.step(by: -1).size) + end + + def test_range_step_size + assert_equal(10, (1..10).step.size) + assert_equal(9, (1...10).step.size) + assert_equal(5, (1..10).step(2).size) + assert_equal(5, (1...10).step(2).size) + assert_equal(4, (1...9).step(2).size) + assert_equal(Float::INFINITY, (1..).step.size) + + assert_equal(10, (10..1).step(-1).size) + assert_equal(9, (10...1).step(-1).size) + assert_equal(5, (10..1).step(-2).size) + assert_equal(5, (10...1).step(-2).size) + assert_equal(4, (10...2).step(-2).size) + assert_equal(Float::INFINITY, (1..).step(-1).size) + end +end diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index 6568cbff18..22ec62e252 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -632,7 +632,7 @@ class TestEnumerator < Test::Unit::TestCase assert_equal 4, (1..10).step(3).size assert_equal 3, (1...10).step(3).size assert_equal Float::INFINITY, (42..Float::INFINITY).step(2).size - assert_raise(ArgumentError){ (1..10).step(-2).size } + assert_equal 0, (1..10).step(-2).size end def test_size_for_downup_to diff --git a/test/ruby/test_numeric.rb b/test/ruby/test_numeric.rb index 2026f3b155..e48c3448e8 100644 --- a/test/ruby/test_numeric.rb +++ b/test/ruby/test_numeric.rb @@ -260,11 +260,11 @@ class TestNumeric < Test::Unit::TestCase 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_raise(ArgumentError) { 1.step(10, "1") { } } assert_raise(ArgumentError) { 1.step(10, "1").size } assert_raise(TypeError) { 1.step(10, nil) { } } - assert_raise(TypeError) { 1.step(10, nil).size } + assert_nothing_raised { 1.step(10, 0).size } + assert_nothing_raised { 1.step(10, nil).size } assert_nothing_raised { 1.step(by: 0, to: nil) } assert_nothing_raised { 1.step(by: 0, to: nil).size } assert_nothing_raised { 1.step(by: 0) } @@ -272,6 +272,14 @@ class TestNumeric < Test::Unit::TestCase assert_nothing_raised { 1.step(by: nil) } assert_nothing_raised { 1.step(by: nil).size } + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(10, by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: nil)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: 2, to: 10)) + assert_kind_of(Enumerator::ArithmeticSequence, 1.step(by: -1)) + bug9811 = '[ruby-dev:48177] [Bug #9811]' assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil) {} } assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil).size } diff --git a/test/ruby/test_range.rb b/test/ruby/test_range.rb index eada19cfb6..a845dc4929 100644 --- a/test/ruby/test_range.rb +++ b/test/ruby/test_range.rb @@ -222,7 +222,11 @@ class TestRange < Test::Unit::TestCase (0..).step(2) {|x| a << x; break if a.size == 10 } assert_equal([0, 2, 4, 6, 8, 10, 12, 14, 16, 18], a) - assert_raise(ArgumentError) { (0..10).step(-1) { } } + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step) + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step(2)) + assert_kind_of(Enumerator::ArithmeticSequence, (0..10).step(0.5)) + assert_kind_of(Enumerator::ArithmeticSequence, (10..0).step(-1)) + assert_raise(ArgumentError) { (0..10).step(0) { } } assert_raise(ArgumentError) { (0..).step(-1) { } } assert_raise(ArgumentError) { (0..).step(0) { } }