I would like to iterate through different ruby arrays (and possibly开发者_StackOverflow社区 hashes). I don't really want to have to maintain an index to keep track the position I am in for each array. It's not because I'm lazy, but I am used to the C++ way of using iterator, which I think is less error prone.
So is there a way to get an iterator in ruby, like we do in c++ (this example does not do much but it is just for the sake of the example):
std::set< MyObject >::iterator iter1 = set1.begin();
std::set< MyObject >::iterator iter2 = set2.begin();
while(iter1 != set1.end() && iter2 != set2.end()
{
if (iter1->timestamp > iter2->timestamp)
++iter2;
else
++iter1;
}
The Enumerable methods only iterate if you provide a block, otherwise they return an iterator that is similar to the C++ ones. For example, in irb
:
>> e = [1,2,3,4].each
=> #<Enumerator: [1, 2, 3, 4]:each>
>> e.next
=> 1
The tricky thing is that .next
is pretty much like e++
in C++ as it returns the current value and increments the iterator. There is a .rewind
method but that resets the iterator to the beginning rather than going back just one step.
I don't know of a convenient way to detect the end of an iterator (except catching the StopIteration
exception) or determining how large the iterator is.
Presumably, you're supposed to grab the iterator, pass it to some method, and the method does a iter.each { |x| something_interesting(x) }
of some sort.
So, there are iterators but you can't really transliterate your C++ straight into Ruby. OTOH, you shouldn't transliterate your C++ into Ruby, you should write Ruby in Ruby and C++ in C++.
It's not quite clear to my what exactly the result of your example should be, therefore I cannot test whether this conforms to your specifications, but it appears to be roughly what you are looking for:
iter1 = set1.each
iter2 = set2.each
loop do
if iter1.peek.timestamp > iter2.peek.timestamp
iter2.next
else
iter1.next
end
end
Enumerator#peek
is roughly equivalent to dereferencing the iterator in C++ (although it peeks at the next value instead of the current one, which means that there may be a fencepost error in my code). Enumerator#next
advances the enumerator and returns the next value. The end of the enumerator is signalled by raising a StopIteration
exception, which however is handled automatically and correctly by Kernel#loop
.
Regarding Jörg's statement:
(although it peeks at the next value instead of the current one, which means that there may be a fencepost error in my code).
it's trivial to test in a version of Ruby containing Enumerator#peek (1.8.7 does not appear to)
e = [10,20,30].each => #<Enumerator: [10, 20, 30]:each>
e.peek => 10
The secret is, until you fetch the first entry with .next, the iterator is "before" the start, so the .peek will show you the item .next would return.
Thus, no fencepost error...
What I can't find is a way to test exhaustion without just having to rely on the exception being thrown. It would be nice to have a .finished? method...
精彩评论