I have just come across some interesting Enumerator behaviour. There seems to be some dependency in Enumerator on position in an Enumerable - once you have peeked the end of the Enumerable and a StopIteration has been raised, no extension of the Enumerable is noted by the Enumerator.
Two examples demonstrate:
a=[1, 2, 3]
e=a.each
=> #<Enumerator: [1, 2, 3]:each>
2.4.0 :027 > e.next
=> 1
2.4.0 :028 > a.insert(1, 4)
=> [1, 4, 2, 3]
2.4.0 :029 > e.next
=> 4
2.4.0 :031 > e.next
=> 2
OK, so far, so good. But what about this. Let's define a method to extend an array when we hit the end:
def a_ext(a,enum)
enum.peek
rescue StopIteration
a << a[-1] + 1
end
Now let's see what happens when we use it
2.4.0 :012 > a=[1, 2, 3]
=> [1, 2, 3]
2.4.0 :013 > e = a.each
=> #<Enumerator: [1, 2, 3]:each>
2.4.0 :016 > 3.times{e.next}
=> 3
We have reached the end of the array - so call a_ext to extend the array
2.4.0 :018 > a_ext(a,e)
=> [1, 2, 3, 4]
2.4.0 :019 > e.peek
StopIteration: iteration reached an end
????!!
It looks like once you have hit StopIteration, the Enumerator won't check again to see if the Array (I guess in general, an Enumerable) has been extended.
Is this expected behaviour? a bug? a feature?
Why might you want to do this. Well - with a Hash you can set a default value by passing Hash::new a block - and you can pass a block to Array::new. But the block that Array::new takes as an argument only has the index as a key, not the Array and the index (like Hash::new whose block yields the hash and the key). So this makes it extremely ugly and difficult to build an array that can be extended while enumerating through it.
For example, image an appointments diary where you want to enumerate through to find the first free day. This is naturally an Array rather than a Hash (as it is ordered), but it is very hard to extend while iterating through it.
Thoughts?
findorfind_indexinstead of using an enumerator. - sschmeck