Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a built-in way to check if #next or #peek will raise StopIteration?

I'm working with a few iterators where I have to do something along these lines (enum is an enumerator)

enums_with_zero << enum.rewind if enum.peek == 0

which normally works fine, but this is after there's already been #next called on the enum a few times. The issue with this is that the enum could be at the end and with a few values passed for enum, I have hit the issue where enum.peek raises StopIteration because the enum is completed. Is there a way I could put in a guard to check if enum.peek or enum.next will cause StopIteration before I call it. Something that would have the behavior of this for example?

class Enumerator
  def has_next?
    begin
      peek && true
    rescue StopIteration
      false
    end
  end
end
like image 819
Eli Sadoff Avatar asked Oct 17 '25 10:10

Eli Sadoff


1 Answers

You can rescue the StopIteration explicitly, but there's also the idea that the loop method internally rescues a StopIteration exception by simply exiting the loop. (Inside loop, raise StopIteration has the same effect as break.)

This code simply exits the loop when you try to peek past the end:

a = %w(a b c d e).to_enum

loop do
  print a.peek
  a.next
end

The code outputs abcde. (It also transparently raises and rescues StopIteration.)

So, if you want to simply ignore the StopIteration exception when you try to peek past the end, just use loop.

Of course, once you peek past the end, you'll get dumped out of the loop. If you don't want that, you can use while and rescue to customize behavior. For example, if you want to avoid exiting if you peek past the end, and exit when you iterate past the end using next, you could do something like this:

a = %w(a b c d e).to_enum

while true
  begin  
    print a.peek
  rescue StopIteration
    print "\nTried to peek past the end of the enum.\nWe're gonna overlook that.\n"
  end
  x = a.next rescue $!
  break if x.class == StopIteration
end

p 'All done!'

The last two lines in the loop do the same thing as this, which you could use instead:

begin
  a.next
rescue StopIteration
  break
end

A point to make is that handling StopIteration is Ruby's intended way of dealing with getting to the end of an iterator. Quoting from Matz's book The Ruby Programming Language:

External iterators are quite simple to use: just call next each time you want another element. When there are no more elements left, next will raise a StopIteration exception. This may seem unusual—an exception is raised for an expected termination condition rather than an unexpected and exceptional event. (StopIteration is a descendant of StandardError and IndexError; note that it is one of the only exception classes that does not have the word “error” in its name.) Ruby follows Python in this external iteration technique. By treating loop termination as an exception, it makes your looping logic extremely simple; there is no need to check the return value of next for a special end-of-iteration value, and there is no need to call some kind of next? predicate before calling next.

like image 55
BobRodes Avatar answered Oct 19 '25 23:10

BobRodes



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!