1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

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
This commit is contained in:
mrkn 2018-08-06 09:08:28 +00:00
parent 1777e39c2a
commit f15069338d
11 changed files with 883 additions and 47 deletions

View file

@ -15,6 +15,10 @@
#include "internal.h" #include "internal.h"
#include "id.h" #include "id.h"
#ifdef HAVE_FLOAT_H
#include <float.h>
#endif
/* /*
* Document-class: Enumerator * Document-class: Enumerator
* *
@ -105,6 +109,7 @@ VALUE rb_cEnumerator;
static VALUE rb_cLazy; static VALUE rb_cLazy;
static ID id_rewind, id_new, id_yield, id_to_enum; 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_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; static VALUE sym_each, sym_cycle;
#define id_call idCall #define id_call idCall
@ -156,6 +161,8 @@ struct proc_entry {
static VALUE generator_allocate(VALUE klass); static VALUE generator_allocate(VALUE klass);
static VALUE generator_init(VALUE obj, VALUE proc); static VALUE generator_init(VALUE obj, VALUE proc);
static VALUE rb_cArithSeq;
/* /*
* Enumerator * Enumerator
*/ */
@ -2375,6 +2382,391 @@ stop_result(VALUE self)
return rb_attr_get(self, id_result); 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 void
InitVM_Enumerator(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, "yield", yielder_yield, -2);
rb_define_method(rb_cYielder, "<<", yielder_yield_push, -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 */ rb_provide("enumerator.so"); /* for backward compatibility */
} }
@ -2469,6 +2877,10 @@ Init_Enumerator(void)
id_method = rb_intern("method"); id_method = rb_intern("method");
id_force = rb_intern("force"); id_force = rb_intern("force");
id_to_enum = rb_intern("to_enum"); 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_each = ID2SYM(id_each);
sym_cycle = ID2SYM(rb_intern("cycle")); sym_cycle = ID2SYM(rb_intern("cycle"));

View file

@ -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); 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) */ /* error.c (export) */
int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data); int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data);
NORETURN(void rb_unexpected_type(VALUE,int)); NORETURN(void rb_unexpected_type(VALUE,int));

View file

@ -2581,10 +2581,9 @@ num_step_negative_p(VALUE num)
} }
static int 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; VALUE hash;
int desc;
argc = rb_scan_args(argc, argv, "02:", to, step, &hash); argc = rb_scan_args(argc, argv, "02:", to, step, &hash);
if (!NIL_P(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 (values[1] != Qundef) {
if (argc > 1) rb_raise(rb_eArgError, "step is given twice"); 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 { else {
/* compatibility */ /* compatibility */
if (argc > 1 && NIL_P(*step)) { if (argc > 1 && NIL_P(*step)) {
rb_raise(rb_eTypeError, "step must be numeric"); rb_raise(rb_eTypeError, "step must be numeric");
} }
if (rb_equal(*step, INT2FIX(0))) { if (!allow_zero_step && rb_equal(*step, INT2FIX(0))) {
rb_raise(rb_eArgError, "step can't be 0"); rb_raise(rb_eArgError, "step can't be 0");
} }
} }
if (NIL_P(*step)) { if (NIL_P(*step)) {
*step = INT2FIX(1); *step = INT2FIX(1);
} }
desc = num_step_negative_p(*step); desc = num_step_negative_p(*step);
if (NIL_P(*to)) { if (fix_nil && NIL_P(*to)) {
*to = desc ? DBL2NUM(-HUGE_VAL) : DBL2NUM(HUGE_VAL); *to = desc ? DBL2NUM(-HUGE_VAL) : DBL2NUM(HUGE_VAL);
} }
return desc; 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 static VALUE
num_step_size(VALUE from, VALUE args, VALUE eobj) 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; int argc = args ? RARRAY_LENINT(args) : 0;
const VALUE *argv = args ? RARRAY_CONST_PTR(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); 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; VALUE to, step;
int desc, inf; 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))) { if (rb_equal(step, INT2FIX(0))) {
inf = 1; inf = 1;
} }

18
range.c
View file

@ -388,18 +388,26 @@ range_step(int argc, VALUE *argv, VALUE range)
{ {
VALUE b, e, step, tmp; VALUE b, e, step, tmp;
RETURN_SIZED_ENUMERATOR(range, argc, argv, range_step_size);
b = RANGE_BEG(range); b = RANGE_BEG(range);
e = RANGE_END(range); e = RANGE_END(range);
if (argc == 0) { if (argc == 0) {
step = INT2FIX(1); step = INT2FIX(1);
} }
else { else {
rb_scan_args(argc, argv, "01", &step); rb_scan_args(argc, argv, "01", &step);
step = check_step_domain(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)) { if (FIXNUM_P(b) && NIL_P(e) && FIXNUM_P(step)) {
long i = FIX2LONG(b), unit = FIX2LONG(step); long i = FIX2LONG(b), unit = FIX2LONG(step);
do { do {

View file

@ -259,15 +259,15 @@ describe :numeric_step, :shared => true do
describe "when no block is given" do describe "when no block is given" do
it "returns an Enumerator when step is 0" 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 end
it "returns an Enumerator when not passed a block and self > stop" do 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 end
it "returns an Enumerator when not passed a block and self < stop" do 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 end
it "returns an Enumerator that uses the given step" do it "returns an Enumerator that uses the given step" do

View file

@ -23,23 +23,23 @@ describe "Numeric#step" do
describe "when no block is given" do describe "when no block is given" do
it "returns an Enumerator when step is 0" 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 end
it "returns an Enumerator when step is 0.0" do 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 end
describe "returned Enumerator" do describe "returned Enumerator" do
describe "size" do describe "size" do
it "raises an ArgumentError when step is 0" do it "raises an ArgumentError when step is 0" do
enum = 1.step(5, 0) enum = 1.step(5, 0)
lambda { enum.size }.should raise_error(ArgumentError) enum.size.should == Float::INFINITY
end end
it "raises an ArgumentError when step is 0.0" do it "raises an ArgumentError when step is 0.0" do
enum = 1.step(2, 0.0) enum = 1.step(2, 0.0)
lambda { enum.size }.should raise_error(ArgumentError) enum.size.should == Float::INFINITY
end end
end end
end end

View file

@ -5,9 +5,9 @@ describe "Range#step" do
ScratchPad.record [] ScratchPad.record []
end 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 = (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]) enum.to_a.should eql([1, 5, 9])
end end
@ -263,7 +263,7 @@ describe "Range#step" do
end end
describe "when no block is given" do describe "when no block is given" do
describe "returned Enumerator" do describe "returned Enumerator::ArithmeticSequence" do
describe "size" do describe "size" do
it "raises a TypeError if step does not respond to #to_int" do it "raises a TypeError if step does not respond to #to_int" do
obj = mock("Range#step non-integer") obj = mock("Range#step non-integer")
@ -279,19 +279,9 @@ describe "Range#step" do
lambda { enum.size }.should raise_error(TypeError) lambda { enum.size }.should raise_error(TypeError)
end end
it "raises an ArgumentError if step is 0" do it "returns Float::INFINITY for zero step" do
enum = (-1..1).step(0) (-1..1).step(0).size.should == Float::INFINITY
lambda { enum.size }.should raise_error(ArgumentError) (-1..1).step(0.0).size.should == Float::INFINITY
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)
end end
it "returns the ceil of range size divided by the number of steps" do 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 (-5...5).step(2).size.should == 5
end 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 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..1.0).step(0.5).size.should == 5
(-1.0...1.0).step(0.5).size.should == 4 (-1.0...1.0).step(0.5).size.should == 4

View file

@ -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

View file

@ -632,7 +632,7 @@ class TestEnumerator < Test::Unit::TestCase
assert_equal 4, (1..10).step(3).size assert_equal 4, (1..10).step(3).size
assert_equal 3, (1...10).step(3).size assert_equal 3, (1...10).step(3).size
assert_equal Float::INFINITY, (42..Float::INFINITY).step(2).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 end
def test_size_for_downup_to def test_size_for_downup_to

View file

@ -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) { } }
assert_raise(ArgumentError) { 1.step(10, 1, 0).size } assert_raise(ArgumentError) { 1.step(10, 1, 0).size }
assert_raise(ArgumentError) { 1.step(10, 0) { } } 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") { } }
assert_raise(ArgumentError) { 1.step(10, "1").size } assert_raise(ArgumentError) { 1.step(10, "1").size }
assert_raise(TypeError) { 1.step(10, nil) { } } 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) }
assert_nothing_raised { 1.step(by: 0, to: nil).size } assert_nothing_raised { 1.step(by: 0, to: nil).size }
assert_nothing_raised { 1.step(by: 0) } 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) }
assert_nothing_raised { 1.step(by: nil).size } 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]' bug9811 = '[ruby-dev:48177] [Bug #9811]'
assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil) {} } assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil) {} }
assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil).size } assert_raise(ArgumentError, bug9811) { 1.step(10, foo: nil).size }

View file

@ -222,7 +222,11 @@ class TestRange < Test::Unit::TestCase
(0..).step(2) {|x| a << x; break if a.size == 10 } (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_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..10).step(0) { } }
assert_raise(ArgumentError) { (0..).step(-1) { } } assert_raise(ArgumentError) { (0..).step(-1) { } }
assert_raise(ArgumentError) { (0..).step(0) { } } assert_raise(ArgumentError) { (0..).step(0) { } }