Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby C++ style iterator

Tags:

c++

iterator

ruby

I would like to iterate through different ruby arrays (and possibly 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;
}
like image 866
0x26res Avatar asked Dec 17 '22 16:12

0x26res


2 Answers

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

like image 170
mu is too short Avatar answered Jan 04 '23 12:01

mu is too short


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.

like image 36
Jörg W Mittag Avatar answered Jan 04 '23 12:01

Jörg W Mittag