Frage

I'm surprised that Enumerator#each doesn't start off at the current position in the sequence.

o = Object.new

def o.each
  yield 1
  yield 2
  yield 3
end

e = o.to_enum
puts e.next
puts e.next
e.each{|x| puts x}
# I expect to see 1,2,3 but I see 1,2,1,2,3
# apparently Enumerator's each (inherited from Enumerable) restarts the sequence!

Am I doin' it wrong? Is there a way to maybe construct another Enumerator (from e) that will have the expected each behavior?

War es hilfreich?

Lösung

You're not doing it wrong, that's just not the semantics defined for Enumerator#each. You could make a derivative enumerator that only iterates from current position to end:

class Enumerator
  def enum_the_rest
    Enumerator.new { |y| loop { y << self.next } }
  end
end

o = Object.new
def o.each
  yield 1
  yield 2
  yield 3
end

e = o.to_enum
=> #<Enumerator: ...>
e.next
=> 1
e2 = e.enum_the_rest
=> #<Enumerator: ...>
e2.each { |x| puts x }
=> 2
=> 3

And, BTW, each doesn't restart the sequence, it just always runs over the entire span. Your enumerator still knows where it is in relation to the next next call.

e3 = o.to_enum
e3.next
=> 1
e3.next
=> 2
e3.map(&:to_s)
=> ["1", "2", "3"]
e3.next
=> 3

Andere Tipps

Enumerator#next and Enumerator#each work on the object differently. Per the documentation for #each (emphasis mine):

Iterates over the block according to how this Enumerable was constructed. If no block is given, returns self.

So #each always behaves based on the original setup, not on the current internal state. If you quickly peak at the source you'll see that rb_obj_dup is called to setup a new enumerator.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top