Perhaps more messy than a loop without this feature, but a
next_and_skip_next_element
should be perfectly clear to a reader. I mean, if we have
'complicated' constructs such as 'inject', we will be able to cope
with 'skip_next_element', won't we?
I think you could implement something like that via a customized each
and a catch/throw, yes. It'd be a bit involved but certainly doable.
[ snip ]
You know that those are not the same. The first one is looking back,
the second could look forward.
I guess I misunderstood the goal. I was going for "the cleanest way to
skip elements following those satisfying some condition", rather than
"the cleanest way to skip elements following those satisfying some
condition".
But from your new example it's apparent that you really do want
iteration-with-lookahead (or an equivalent), and the ability to consume
more than one input item at a time. That sounds suspiciously like
parsing a grammar...
module Enumerable
def each_with_next(last=nil)
prev=nil
first=true
each do |elt|
begin
if first
first=false
next
else
yield prev,elt
end
ensure # in case of 'next'
prev = elt
end
end
yield prev,last
end
end
It's a subtle detail, but you'd probably want to wrap that yield
prev,last in a 1.times {} so retry and next work as expected. You've
also got to handle break specially, and handle the empty case.
module Enumerable
def each_with_next(last=nil)
prev = nil
first = true
skip_last = true # skip last iteration if empty
each do |elt|
skip_last = true # skip last iteration if 'break' used
begin
if first
first = false
next
else
yield prev, elt
end
ensure # in case of 'next'
prev = elt
end
skip_last = false # 'break' wasn't used this time around
end
1.times { yield prev, last } unless skip_last
end
end
Messing with the future makes messy code. At least it's all confined in
one place.
Ah, I did just notice that 'retry' won't work with this, as both 'next'
and 'retry' will hit the ensure clause. That might not be a problem for
your purposes, though. I notice a lot of things (like IO#each) don't
really support retry either.
Just for fun, here is an alternate approach for the lookahead. You'll
probably recognize it as the C-like solution, dressed up a bit.
a = %w(a b b c b d e).map { |s| s.intern }
ArrayStream.new(a).each do |elt|
if elt == :b && s.la == :b
puts "double_b"
s.skip
else
puts elt
end
end
Given:
class ArrayStream
include Enumerable
def initialize( array )
@array = array
@pos = 0
end
def empty?
@pos >= @array.size
end
def get
pos = @pos
@pos += 1
@array[pos]
end
def la(offset=1)
@array[@pos+offset]
end
def skip(offset=1)
@pos += offset
end
def each
# 'retry' won't retry, but then that
# doesn't work for IO#each either
yield get until empty?
end
end
You know, there has GOT to be a simpler way to do this...
What's the actual problem you're trying to solve? I'll bet there's a
more "outside the box" solution. As I said, you're basically parsing a
simple grammar:
GRAMMAR := ( DOUBLE_B | LETTER )*
DOUBLE_B := 'b' 'b'
LETTER := 'a' | 'b' | 'c' | 'd' | ... | 'z'
If your input is strings, regexps are the tool for this (it's a regular
grammar). If it's something else, you still might want to bring some
parsing-related techniques and/or libraries to bear.
Of course, if your real problem is not much more complex than your
example, I'd say go with the index variable and while loop.
-mental
···
On Thu, 2005-12-08 at 18:07 +0900, Patrick Gundlach wrote: