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

Overloads:

  • #step(s = 1) {|element| ... } ⇒ self

    Yields:

    • (element)

    Returns:

    • (self)


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 (iterating forward)
        // if begin > end, the cycle performs while value > end (iterating 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;
}