Eli Sadoff
Eli Sadoff

Reputation: 7308

Is there a way to specify a multi-step in Ruby?

I'm working with some lazy iteration, and would like to be able to specify a multiple step for this iteration. This means that I want the step to alternate between a and b. So, if I had this as a range (not lazy just for simplification)

(1..20).step(2, 4)

I would want my resulting range to be

1 # + 2 =
3 # + 4 = 
7 # + 2 =
9 # + 4 =
13 # + 2 = 
15 # + 4 = 
19 # + 2 = 21 (out of range, STOP ITERATION)

However, I cannot figure out a way to do this. Is this at all possible in Ruby?

Upvotes: 2

Views: 82

Answers (2)

Eric Duminil
Eric Duminil

Reputation: 54303

You could use a combination of cycle and Enumerator :

class Range
  def multi_step(*steps)
    a = min
    Enumerator.new do |yielder|
      steps.cycle do |step|
        yielder << a
        a += step
        break if a > max
      end
    end
  end
end

p (1..20).multi_step(2, 4).to_a
#=> [1, 3, 7, 9, 13, 15, 19]

Note that the first element is 1, because the first element of (1..20).step(2) is also 1.

It takes exclude_end? into account :

p (1...19).multi_step(2, 4).to_a
#=> [1, 3, 7, 9, 13, 15]

And can be lazy :

p (0..2).multi_step(1,-1).first(20)
#=> [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
p (0..Float::INFINITY).multi_step(*(1..100).to_a).lazy.map{|x| x*2}.first(20)
#=> [0, 2, 6, 12, 20, 30, 42, 56, 72, 90, 110, 132, 156, 182, 210, 240, 272, 306, 342, 380]

Here's a variant of FizzBuzz, which generates all the multiples of 3 or 5 but not 15 :

p (3..50).multi_step(2,1,3,1,2,6).to_a
#=> [3, 5, 6, 9, 10, 12, 18, 20, 21, 24, 25, 27, 33, 35, 36, 39, 40, 42, 48, 50]

Upvotes: 3

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84443

Ruby doesn't have a built-in method for stepping with multiple values. However, if you don't actually need a lazy method, you can use Enumerable#cycle with an accumulator. For example:

range = 1..20
accum = range.min
[2, 4].cycle(range.max) { |step| accum += step; puts accum }

Alternatively, you could construct your own lazy enumerator with Enumerator::Lazy. That seems like overkill for the given example, but may be useful if you have an extremely large Range object.

Upvotes: 1

Related Questions