Toggling values with Array#cycle and next
Question
What's the return value of the following Ruby code?
toggler = %w(start stop).cycle
toggler.next
toggler.next
toggler.next # => ???
The correct answer is
-
nil -
"stop" -
"start"Correct -
It raises
IndexError
Explanation
TL;DR
Array#cycle without a block returns an Enumerator that repeats the array's elements forever. Each next call advances it by one element, wrapping around at the end: "start", "stop", then "start" again. The third call returns "start".
Step by step
toggler = %w(start stop).cycle
toggler.next # => "start"
toggler.next # => "stop"
toggler.next # => "start"
toggler.peek # => "stop"
next performs external iteration: instead of handing a block to the enumerator, you pull values out one at a time, and the enumerator remembers its position between calls. peek shows the upcoming element without advancing, and rewind resets the position to the start.
Because the enumerator is infinite, methods that consume it all would never return. Lazy-friendly methods work fine:
%w(start stop).cycle.first(5) # => ["start", "stop", "start", "stop", "start"]
Edge cases
cycle(n) repeats the collection a finite number of times. Once a finite enumerator is exhausted, next raises StopIteration:
two = %w(a b).cycle(1)
two.next # => "a"
two.next # => "b"
begin
two.next
rescue StopIteration => e
e.class # => StopIteration
end
StopIteration is the one exception Kernel#loop rescues silently, which is why loop { work(enum.next) } exits cleanly when the enumerator runs out.
With a block, cycle iterates immediately instead of returning an enumerator: [1, 2].cycle { |x| ... } loops forever, and [1, 2].cycle(2) { |x| ... } runs the block four times and returns nil.
Share this quiz
Comments
No comments yet. Be the first.