开发者

Ruby C++ style iterator

开发者 https://www.devze.com 2023-02-18 06:20 出处:网络
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

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...

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号