Method: Range#step
- Defined in:
- range.c
#step(s = 1) {|element| ... } ⇒ self #step(s = 1) ⇒ Object
Iterates over the elements of range in steps of s
. The iteration is performed by +
operator:
(0..6).step(2) { puts _1 } #=> 1..5
# Prints: 0, 2, 4, 6
# Iterate between two dates in step of 1 day (24 hours)
(Time.utc(2022, 2, 24)..Time.utc(2022, 3, 1)).step(24*60*60) { puts _1 }
# Prints:
# 2022-02-24 00:00:00 UTC
# 2022-02-25 00:00:00 UTC
# 2022-02-26 00:00:00 UTC
# 2022-02-27 00:00:00 UTC
# 2022-02-28 00:00:00 UTC
# 2022-03-01 00:00:00 UTC
If + step
decreases the value, iteration is still performed when step begin
is higher than the end
:
(0..6).step(-2) { puts _1 }
# Prints nothing
(6..0).step(-2) { puts _1 }
# Prints: 6, 4, 2, 0
(Time.utc(2022, 3, 1)..Time.utc(2022, 2, 24)).step(-24*60*60) { puts _1 }
# Prints:
# 2022-03-01 00:00:00 UTC
# 2022-02-28 00:00:00 UTC
# 2022-02-27 00:00:00 UTC
# 2022-02-26 00:00:00 UTC
# 2022-02-25 00:00:00 UTC
# 2022-02-24 00:00:00 UTC
When the block is not provided, and range boundaries and step are Numeric, the method returns Enumerator::ArithmeticSequence.
(1..5).step(2) # => ((1..5).step(2))
(1.0..).step(1.5) #=> ((1.0..).step(1.5))
(..3r).step(1/3r) #=> ((..3/1).step((1/3)))
Enumerator::ArithmeticSequence can be further used as a value object for iteration or slicing of collections (see Array#[]). There is a convenience method #% with behavior similar to step
to produce arithmetic sequences more expressively:
# Same as (1..5).step(2)
(1..5) % 2 # => ((1..5).%(2))
In a generic case, when the block is not provided, Enumerator is returned:
('a'..).step('b') #=> #<Enumerator: "a"..:step("b")>
('a'..).step('b').take(3) #=> ["a", "ab", "abb"]
If s
is not provided, it is considered 1
for ranges with numeric begin
:
(1..5).step { p _1 }
# Prints: 1, 2, 3, 4, 5
For non-Numeric ranges, step absence is an error:
(Time.utc(2022, 3, 1)..Time.utc(2022, 2, 24)).step { p _1 }
# raises: step is required for non-numeric ranges (ArgumentError)
For backward compatibility reasons, String ranges support the iteration both with string step and with integer step. In the latter case, the iteration is performed by calculating the next values with String#succ:
('a'..'e').step(2) { p _1 }
# Prints: a, c, e
('a'..'e').step { p _1 }
# Default step 1; prints: a, b, c, d, e
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 |
# File 'range.c', line 483 static VALUE range_step(int argc, VALUE *argv, VALUE range) { VALUE b, e, v, step; int c, dir; b = RANGE_BEG(range); e = RANGE_END(range); v = b; const VALUE b_num_p = rb_obj_is_kind_of(b, rb_cNumeric); const VALUE e_num_p = rb_obj_is_kind_of(e, rb_cNumeric); // For backward compatibility reasons (conforming to behavior before 3.4), String/Symbol // supports both old behavior ('a'..).step(1) and new behavior ('a'..).step('a') // Hence the additional conversion/additional checks. const VALUE str_b = rb_check_string_type(b); const VALUE sym_b = SYMBOL_P(b) ? rb_sym2str(b) : Qnil; if (rb_check_arity(argc, 0, 1)) step = argv[0]; else { if (b_num_p || !NIL_P(str_b) || !NIL_P(sym_b) || (NIL_P(b) && e_num_p)) step = INT2FIX(1); else rb_raise(rb_eArgError, "step is required for non-numeric ranges"); } const VALUE step_num_p = rb_obj_is_kind_of(step, rb_cNumeric); if (step_num_p && b_num_p && rb_equal(step, INT2FIX(0))) { rb_raise(rb_eArgError, "step can't be 0"); } if (!rb_block_given_p()) { // This code is allowed to create even beginless ArithmeticSequence, which can be useful, // e.g., for array slicing: // ary[(..-1) % 3] if (step_num_p && ((b_num_p && (NIL_P(e) || e_num_p)) || (NIL_P(b) && e_num_p))) { return rb_arith_seq_new(range, ID2SYM(rb_frame_this_func()), argc, argv, range_step_size, b, e, step, EXCL(range)); } // ...but generic Enumerator from beginless range is useless and probably an error. if (NIL_P(b)) { rb_raise(rb_eArgError, "#step for non-numeric beginless ranges is meaningless"); } RETURN_SIZED_ENUMERATOR(range, argc, argv, 0); } if (NIL_P(b)) { rb_raise(rb_eArgError, "#step iteration for beginless ranges is meaningless"); } if (FIXNUM_P(b) && NIL_P(e) && FIXNUM_P(step)) { /* perform summation of numbers in C until their reach Fixnum limit */ long i = FIX2LONG(b), unit = FIX2LONG(step); do { rb_yield(LONG2FIX(i)); i += unit; /* FIXABLE+FIXABLE never overflow */ } while (FIXABLE(i)); b = LONG2NUM(i); /* then switch to Bignum API */ for (;; b = rb_big_plus(b, step)) rb_yield(b); } else if (FIXNUM_P(b) && FIXNUM_P(e) && FIXNUM_P(step)) { /* fixnums are special: summation is performed in C for performance */ long end = FIX2LONG(e); long i, unit = FIX2LONG(step); if (unit < 0) { if (!EXCL(range)) end -= 1; i = FIX2LONG(b); while (i > end) { rb_yield(LONG2NUM(i)); i += unit; } } else { if (!EXCL(range)) end += 1; i = FIX2LONG(b); while (i < end) { rb_yield(LONG2NUM(i)); i += unit; } } } else if (b_num_p && step_num_p && ruby_float_step(b, e, step, EXCL(range), TRUE)) { /* done */ } else if (!NIL_P(str_b) && FIXNUM_P(step)) { // backwards compatibility behavior for String only, when no step/Integer step is passed // See discussion in https://bugs.ruby-lang.org/issues/18368 VALUE iter[2] = {INT2FIX(1), step}; if (NIL_P(e)) { rb_str_upto_endless_each(str_b, step_i, (VALUE)iter); } else { rb_str_upto_each(str_b, e, EXCL(range), step_i, (VALUE)iter); } } else if (!NIL_P(sym_b) && FIXNUM_P(step)) { // same as above: backward compatibility for symbols VALUE iter[2] = {INT2FIX(1), step}; if (NIL_P(e)) { rb_str_upto_endless_each(sym_b, sym_step_i, (VALUE)iter); } else { rb_str_upto_each(sym_b, rb_sym2str(e), EXCL(range), sym_step_i, (VALUE)iter); } } else if (NIL_P(e)) { // endless range for (;; v = rb_funcall(v, id_plus, 1, step)) rb_yield(v); } else if (b_num_p && step_num_p && r_less(step, INT2FIX(0)) < 0) { // iterate backwards, for consistency with ArithmeticSequence if (EXCL(range)) { for (; r_less(e, v) < 0; v = rb_funcall(v, id_plus, 1, step)) rb_yield(v); } else { for (; (c = r_less(e, v)) <= 0; v = rb_funcall(v, id_plus, 1, step)) { rb_yield(v); if (!c) break; } } } else if ((dir = r_less(b, e)) == 0) { if (!EXCL(range)) { rb_yield(v); } } else if (dir == r_less(b, rb_funcall(b, id_plus, 1, step))) { // Direction of the comparison. We use it as a comparison operator in cycle: // if begin < end, the cycle performs while value < end ( forward) // if begin > end, the cycle performs while value > end ( backward with // a negative step) // One preliminary addition to check the step moves iteration in the same direction as // from begin to end; otherwise, the iteration should be empty. if (EXCL(range)) { for (; r_less(v, e) == dir; v = rb_funcall(v, id_plus, 1, step)) rb_yield(v); } else { for (; (c = r_less(v, e)) == dir || c == 0; v = rb_funcall(v, id_plus, 1, step)) { rb_yield(v); if (!c) break; } } } return range; } |