Forum: Ruby increasing counter whithin loop?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
clr9.10.randomuser (Guest)
on 2005-12-05 16:09
(Received via mailing list)
Hi,

a very basic question...

I'd like to output the sequence "a b d e", by testing if the current
element is == "b" then skip the next element and continue the loop. The
obvious solution doesn't look rubyish to me, how could I use the first
or second attempt to get the desired solution?

Patrick
--------------------------------------------------
a=%w( a b c d e )

# incorrect, outputs "a b c d e"
0.upto(a.size - 1) do |i|
  puts a[i]
  if a[i]=="b"
    # skip next element
    # but i won't get affected
    i += 1
  end
end

# incorrect, outputs "a b c d e"
for i in 0...a.size
  puts a[i]
  if a[i]=="b"
    # skip next element
    # but i won't get affected
    i += 1
  end
end

# incorrect, outputs nothing... is there a next_next ?
a.each do |elt|
  puts elt
  if elt=="b"
    # skip next element
    # ??
  end
end

# this one works, but is ugly
i=0
while i < a.size
  puts a[i]
  if a[i]=="b"
    # skip next element
    i += 1
  end
  i += 1
end
Pavel S. (Guest)
on 2005-12-05 16:24
a=%w( a b c d e )

seen = false

a.each {|elem|

unless seen
 #do stuff
 puts elem
 seen = true if elem == "b"
else
 seen = false
end
}

cheers,
Pavel
bob.news (Guest)
on 2005-12-05 16:30
(Received via mailing list)
Patrick G. wrote:
> --------------------------------------------------
> a=%w( a b c d e )

>> a=%w( a b c d e )
=> ["a", "b", "c", "d", "e"]
>> a.each do |elt|
?>   next if elt=="b"
>>   puts elt
>> end
a
c
d
e
=> ["a", "b", "c", "d", "e"]
>>

Kind regards

    robert
malte__ (Guest)
on 2005-12-05 16:38
(Received via mailing list)
Patrick G.:
> I'd like to output the sequence "a b d e", by testing if the current
> element is == "b" then skip the next element and continue the loop.

I do not know whether I understood correctly what you want, but you
could
try this:

array = [ :a, :b, :c, :d, :e ]
array.each_with_index do |x, i|
  next if array[i-1] == :b
  print x, ' '
end

Malte
decoux (Guest)
on 2005-12-05 16:42
(Received via mailing list)
>>>>> "M" == Malte M. <removed_email_address@domain.invalid> writes:

 try it, with

   array = [ :a, :b, :c, :d, :e , :b]

M> array.each_with_index do |x, i|
M>   next if array[i-1] == :b
M>   print x, ' '
M> end

 :-)


Guy Decoux
neil (Guest)
on 2005-12-05 17:19
(Received via mailing list)
Patrick G. wrote:
> Hi,
>
> a very basic question...
>
> I'd like to output the sequence "a b d e", by testing if the current
> element is == "b" then skip the next element and continue the loop. The
> obvious solution doesn't look rubyish to me, how could I use the first
> or second attempt to get the desired solution?

array = [1, :skip, :hidden, 2, :skip, :hidden, 3, :skip]

# Zipped to ensure i is uniquely index()ed
# Also makes sure use array non-destructively
zip = array.zip
for i in zip
	if i[0] == :skip
		zip.delete_at(zip.index(i) + 1)
	else
		puts i[0]
	end
end

Output:

1
2
3

--
Neil S. - removed_email_address@domain.invalid

'A republic, if you can keep it.' -- Benjamin Franklin
malte__ (Guest)
on 2005-12-05 17:56
(Received via mailing list)
ts:
>  try it, with
>
>    array = [ :a, :b, :c, :d, :e , :b]

Huh. Right. So it becomes

array.each_with_index do |x, i|
   next if array[i-1] == :b unless i == 0
   print x, ' '
end

Nasty.

Malte
pfharlock (Guest)
on 2005-12-05 18:00
(Received via mailing list)
a = ["a", "b", "c", "d", "e"]

temp = nil

p a.select {|x| if temp == "b" then temp = nil; next end; temp = x;true}


outputs

["a", "b", "d", "e"]

Probably could be cleaner.
James G. (Guest)
on 2005-12-05 18:04
(Received via mailing list)
On Dec 5, 2005, at 9:52 AM, Malte M. wrote:

> ts:
>>  try it, with
>>
>>    array = [ :a, :b, :c, :d, :e , :b]
>
> Huh. Right. So it becomes
>
> array.each_with_index do |x, i|
>    next if array[i-1] == :b unless i == 0

next if i > 0 and array[i - 1] == :b

>    print x, ' '
> end
>
> Nasty.

James Edward G. II
rjseagraves (Guest)
on 2005-12-05 18:16
(Received via mailing list)
Maybe I'm just being thick, but wouldn't it just be easier (and much
closer to what you are actually trying to do) to say

["a", "b", "c", "d", "e"].each do |x|
     puts x unless x == "c"
end

I'll admit I'm also new to Ruby but isn't the above the more "rubyish"
way to achieve what you're trying to do?
clr8.10.randomuser (Guest)
on 2005-12-05 19:10
(Received via mailing list)
Hello again,


thanks for the answers. I have been very unclear what I wanted, but
Malte, Guy and JEGII seemed to have read my mind.

a=%w( a b b d b e )

a.each_with_index do |x, i|
  next if x == "b" and a[i - 1] == "b"
  if x=="b" and a[i+1]=="b"
    puts "double b"
  else
    puts x
  end
end

(slightly different code example, but closer to what I wanted).


I wonder why I cannot change the variable in

for i in 0...x

end

Or am I missing some magic?

Patrick
martindemello (Guest)
on 2005-12-07 11:51
(Received via mailing list)
Patrick G. <removed_email_address@domain.invalid> wrote:
>   if x=="b" and a[i+1]=="b"
>     puts "double b"
>   else
>     puts x
>   end
> end

I'd use Gary W.'s method, and cache the last seen element, rather
than rely upon the index (e.g. if you use a stream rather than an array,
you no longer have access to a[i-i]).

martin
transfire (Guest)
on 2005-12-07 15:40
(Received via mailing list)
Maybe I missed something.

  last = nil
  %w( a b c d e ).select { |e| r = last != 'b' ; last = e ; r }

T.
clr9.10.randomuser (Guest)
on 2005-12-07 18:11
(Received via mailing list)
> Maybe I missed something.

You missed to read my mind; my explanation was very unclear. I wanted
to have something like

"if two 'b' are consecutive, output 'double b' and continue with the
element behind the second".

'a' 'b' 'b' 'c' 'b' 'd'

->

'a' 'double b' 'c' 'b' 'd'

This is why I thought

for i in sequence
  if sequence[i]=='b' and sequence[i+1]=='b'
   output 'double b'
   # increase i, so that the second 'b' won't be seen by the for-loop
   # but ruby won't let me!?
   # i += 1 does nothing
  else
   output sequence[i]
  end
end

would work, but I can't change the i in the for-loop. This is a pity!

Patrick
mental (Guest)
on 2005-12-07 20:38
(Received via mailing list)
Quoting Patrick G. <removed_email_address@domain.invalid>:

>    output sequence[i]
>   end
> end
>
> would work, but I can't change the i in the for-loop. This is a
> pity!

It's probably helpful to realize that Ruby has no for-loop in the
traditional sense.  The above is equivalent to:

 sequence.each do |i|
   if sequence[i]=='b' and sequence[i+1]=='b'
    output 'double b'
    # increase i, so that the second 'b' won't be seen by the
 for-loop
    # but ruby won't let me!?
    # i += 1 does nothing
   else
    output sequence[i]
   end
 end

Written this way, it's probably more obvious why incrementing i
doesn't do what you had expected.

-mental
blargity (Guest)
on 2005-12-07 21:23
(Received via mailing list)
On Wednesday 07 December 2005 11:51, removed_email_address@domain.invalid wrote:
> >   else
>  sequence.each do |i|
>
> Written this way, it's probably more obvious why incrementing i
> doesn't do what you had expected.
>
> -mental

If you're really tied to the traditional for loop, you can use a while
loop
and do the increment yourself at the end of the loop, but usually you
find a
better way to do it in Ruby that doesn't involve going through the chars
one
at a time, and that's why we rarely use the for construct.
clr9.10.randomuser (Guest)
on 2005-12-07 23:09
(Received via mailing list)
> It's probably helpful to realize that Ruby has no for-loop in the
> traditional sense.  The above is equivalent to:
>
>  sequence.each do |i|
> [...]
>  end
>
> Written this way, it's probably more obvious why incrementing i
> doesn't do what you had expected.


Yes, but when writing 'for i in x ... end' I'd expect a for-loop :-)
It would be really nice to be able to increase the counter from within
the loop. I somewhat expected that to work, I can't tell you why.
Perhaps that is what I was used to in other languages?

Patrick
clr9.10.randomuser (Guest)
on 2005-12-08 00:08
(Received via mailing list)
[...]

> If you're really tied to the traditional for loop, you can use a while loop
> and do the increment yourself at the end of the loop, but usually you find a
> better way to do it in Ruby that doesn't involve going through the chars one
> at a time, and that's why we rarely use the for construct.

That is exactly what I am trying to find. I have a list (Array) of
different elements, which I want to render below each other, except
when there are two elements of type 'b', they can be put next to each
other. So I think I need a check like 'if this element is == 'b' and
next element is also == 'b', then render them next to each other. This
rendering has to be known in advance, so I can't use information if
the last element is of type 'b' (with the second occurance of 'b').

Of course, I can write a while loop, but this would be

a) setting some counter to 0
b) accessing the elements via [] (index)
c) checking on counter <=> sequence.length

all which are acceptable, but don't look like the nice ruby builtins
that I am used to. The

sequence.each do |element|
 ....
end

would be nice, but I understand that there is no
'skip_the_next_element'-method. So the next nicer attempt would be

for counter in 0...element.size
  # ...
  increase_counter_by_one_to_skip_one_interation
end

But - contradicting my intuition - doesn't seem to work/exist. So I
have to stick to an ugly while loop.... ;-) So my question is: did I
miss something? Is there any reason why we can't manipulate the
counter within the loop?

Patrick
blargity (Guest)
on 2005-12-08 00:24
(Received via mailing list)
On Wednesday 07 December 2005 16:07, Patrick G. wrote:
> other. So I think I need a check like 'if this element is == 'b' and
> all which are acceptable, but don't look like the nice ruby builtins
>   # ...
>   increase_counter_by_one_to_skip_one_interation
> end
>
> But - contradicting my intuition - doesn't seem to work/exist. So I
> have to stick to an ugly while loop.... ;-) So my question is: did I
> miss something? Is there any reason why we can't manipulate the
> counter within the loop?

No, you can manipulate the counter just fine, just that it won't persist
for
the next iteration of the count.  This is because ruby is providing the
i for
you, but not actually checking it to know where it is, or when it's
done,
unlike similarly worded constructs in C etc.

How about something like:

irb(main):008:0> "abbcdeef".gsub(/(\w)\1/) { |match|
irb(main):009:1*   " double #{match[0, 1]} "
irb(main):010:1> }
=> "a double b cd double e f"
mental (Guest)
on 2005-12-08 01:09
(Received via mailing list)
Quoting Patrick G. <removed_email_address@domain.invalid>:

>   increase_counter_by_one_to_skip_one_interation
> end
>
> But - contradicting my intuition - doesn't seem to work/exist. So
> I have to stick to an ugly while loop.... ;-) So my question is:
> did I miss something?

There's really no difference between:

 for counter in 0...element_size
   ...
 end

and

 (0...element_size).each do |counter|
   ...
 end

Both call Range#each with the given block.

> Is there any reason why we can't manipulate the counter within
> the loop?

'counter' isn't actually a counter.  It's just a parameter of the
block given to Range#each.  While there's probably a real counter
behind the scenes somewhere, it's not exposed to you.

90% of the time you don't need counters or while loops, though.
Even here, there's nothing preventing you from doing e.g.:

 skip = false
 sequence.each do |element|
   if skip
     skip = false
     next
   end
   ...
   # set skip to true to skip the next iteration
   ...
 end

or alternately:

 skip = false
 sequence.each do |element|
   unless skip
     ...
     # set skip to true to skip the next iteration
     ...
   end
   skip = false
 end

-mental
clr9.10.randomuser (Guest)
on 2005-12-08 02:10
(Received via mailing list)
Hi Kevin,

[for i in ..... end ]

> No, you can manipulate the counter just fine, just that it won't persist for
> the next iteration of the count.  This is because ruby is providing the i for
> you, but not actually checking it to know where it is, or when it's done,
> unlike similarly worded constructs in C etc.

That (last part of the sentence) is exactly what confused me. Nobody
else thinks that this persistance would be useful?


Patrick
clr9.10.randomuser (Guest)
on 2005-12-08 02:10
(Received via mailing list)
[...]

>  end
OK, then it makes sense that one cannot manipulate counter. But IMO it
is not intuitive (POLS ;-)) that the counter in the for-loop can't be
changed.


>    # set skip to true to skip the next iteration
>    ...
>  end

Yes, sure, but now it is getting ugly again. I am not looking for any
way to work, but to find a readable, beautiful and ruby-like piece of
code.

Patrick
mental (Guest)
on 2005-12-08 02:27
(Received via mailing list)
Quoting Patrick G. <removed_email_address@domain.invalid>:

> That (last part of the sentence) is exactly what confused me.
> Nobody else thinks that this persistance would be useful?

Well, let's assume we've got persistence.  What would you expect
each of the following to do?

 for i in 1..3
   i = 3
 end

 for i in [ 1, 2, 3 ]
   i = 3
 end

 for s in [ "a", "b", "c" ]
   s = "c"
 end

 for s in $stdin
   s = "hello"
 end

-mental
mental (Guest)
on 2005-12-08 03:15
(Received via mailing list)
Quoting Patrick G. <removed_email_address@domain.invalid>:

> OK, then it makes sense that one cannot manipulate counter. But
> IMO it is not intuitive (POLS ;-)) that the counter in the
> for-loop can't be changed.

Maybe think of it like foreach in TCL [ foreach i $things { ... } ],
or the one for in Javascript [ for (var i in things) { ... } ]
rather than for in C.

As a rule, I think Ruby's going to be extremely counterintuitive
whenever you expect C-like behavior.

> Yes, sure, but now it is getting ugly again. I am not looking for
> any way to work, but to find a readable, beautiful and ruby-like
> piece of code.

That will probably require thinking about it from a different angle.
 The "skip the next (future) element based on the current one" is an
inherently messy concept.

Notching up the counter in a C-style loop is a _concise_ way to do
that, but it's still not particularly easy to reason about if your
loop is nontrivial.

It seems like the cleaner and more Ruby-esque solutions offered so
far involve attacking it from the other end -- "skip the current
element based on the previous one".  It's clearer to remember
things from the past than it is to reach forward into the future.

Along those lines (this is basically Trans' suggestion):

 a = %w(a b c d e)

 prev = nil
 a.each do |elt|
   puts elt unless prev == "b"
   prev = elt
 end

Now, we'll imagine for the moment that Ruby has C-like for loops:

 a = %w(a b c d e)

 for ( i = 0 ; i < a.size ; i += 1 )
   puts a[i]
   i += 1 if a[i] == "b"
 end

I don't know.  I'm not sure I would find the C version clearer if I
weren't a grizzled old C veteran.

Out of curiousity, do you think either of these would be clearer?

 a.each_with_prev do |elt, prev|
   next if prev == "b"
   puts elt
 end

or

 a.each_with_prev do |elt, prev|
   puts elt unless prev == "b"
 end

Don't forget that you're allowed to define convenience methods if
that would clarify things elsewhere:

 module Enumerable
   def each_with_prev( initial=nil )
     prev = initial
     each do |elt|
       begin
         yield elt, prev
       ensure # in case of 'next'
         prev = elt
       end
     end
   end
 end

A lot of Ruby's clarity comes not from being able to write things
clearly in the "raw" language, but from being able to easily
customize the language for your needs.

-mental
leavengood (Guest)
on 2005-12-08 04:32
(Received via mailing list)
On 12/7/05, removed_email_address@domain.invalid 
<removed_email_address@domain.invalid> wrote:
>
> A lot of Ruby's clarity comes not from being able to write things
> clearly in the "raw" language, but from being able to easily
> customize the language for your needs.

Beautifully said, and most true. I think your each_with_prev is the
best way to tackle this problem in a clean Ruby-esque way.

Regards,
Ryan
phurley (Guest)
on 2005-12-08 07:02
(Received via mailing list)
> On 12/7/05, removed_email_address@domain.invalid <removed_email_address@domain.invalid> 
wrote:
> >
> > A lot of Ruby's clarity comes not from being able to write things
> > clearly in the "raw" language, but from being able to easily
> > customize the language for your needs.

+1

You can also try something like this:

module Enumerable
  def each_with_gathered_dups
    group = []
    each do |elem|
      if elem == group.last
        group << elem
      else
        begin
          yield group unless group.empty?
        ensure
          group = [elem]
        end
      end
    end
    yield group
  end
end

# which might work something like this
a = [1, 2, 2, 2, 3, 4, 5, 5, 6, 6, 6, 6]
a.each_with_gathered_dups { |g| p g }

outputing

[1]
[2, 2, 2]
[3]
[4]
[5, 5]
[6, 6, 6, 6]

Notice I chose to wrap the dups as arrays, and single elements as
single element arrays, just another approach, but the lesson is don't
fight the language, grow it to your problem (and learn how to make
better method names than I do :-)

pth
clr10.10.randomuser (Guest)
on 2005-12-08 10:10
(Received via mailing list)
>> That (last part of the sentence) is exactly what confused me.
>> Nobody else thinks that this persistance would be useful?
>
> Well, let's assume we've got persistence.  What would you expect
> each of the following to do?
>
>  for i in 1..3
>    i = 3
>  end

infinite loop.

>
>  for i in [ 1, 2, 3 ]
>    i = 3
>  end

infinite loop.

>
>  for s in [ "a", "b", "c" ]
>    s = "c"
>  end

infinite loop.

>
>  for s in $stdin
>    s = "hello"
>  end

infinite loop.

Same as

do
  ...
while true

But perhaps I don't get your question.

Patrick

(please no cc: to me)
blargity (Guest)
on 2005-12-08 10:18
(Received via mailing list)
On Wednesday 07 December 2005 18:07, Patrick G. wrote:
> [...]
> OK, then it makes sense that one cannot manipulate counter. But IMO it
> is not intuitive (POLS ;-)) that the counter in the for-loop can't be
> changed.

Don't base your sense of "intuitive" on other languages.  It is more
intuitive
that we're going through each element in an array than checking an
arbitrary
integer against a condition in between two semicolons, IMHO.  And my
first
real language was C++.

The generally "normal" way to do string processing in Ruby is with
Regular
Expressions.  This is arguable, but at least I've seen it more.  Play
with my
gsub example and see if you can't get something in one line that does
what
you're looking for (unless you haven't told us the real situation).

> >    # set skip to true to skip the next iteration
> >    ...
> >  end
>
> Yes, sure, but now it is getting ugly again. I am not looking for any
> way to work, but to find a readable, beautiful and ruby-like piece of
> code.

Use RegExps to do your string processing then.  I admit, it's just NOT
the way
you think about string processing when coming from something like C++,
but
it's so much easier to say "Find all the repeated letters and replace
them
with double 'letter'" than "loop through every letter in the string.  If
the
next letter in the string is the same as the current one, then replace
both
of them with 'double letter' by making a new array, copying the current
over,
replacing the letters, copying the end over, and then incrementing the
count
of the for loop to get the language structure to support what I happen
to be
doing."

When you're used to the latter, it's hard to get your head around
regexps, but
it's much nicer, IMHO, and I'm one that had to do the adjustment.  Play
with
it and see if you can't get something that works, or let us know what
you're
really doing, and I'm sure that someone will pop out with a golfed
intuitive
one-ish-liner that fixes your problem. :-)
clr9.10.randomuser (Guest)
on 2005-12-08 10:18
(Received via mailing list)
>> OK, then it makes sense that one cannot manipulate counter. But IMO it
>> is not intuitive (POLS ;-)) that the counter in the for-loop can't be
>> changed.
>
> Don't base your sense of "intuitive" on other languages.

? How else should I expect a behaviour, if not from other languages?


[...]

> Use RegExps to do your string processing then.

Yes, but as soon as my elements aren't strings anymore, I need another
solution.

Patrick
clr9.10.randomuser (Guest)
on 2005-12-08 11:11
(Received via mailing list)
Hi mental,


> Maybe think of it like foreach in TCL [ foreach i $things { ... } ],
> or the one for in Javascript [ for (var i in things) { ... } ]
> rather than for in C.

Hmm, I don't know tcl nor JS well enough.

> As a rule, I think Ruby's going to be extremely counterintuitive
> whenever you expect C-like behavior.

It worked quite well before.

> That will probably require thinking about it from a different angle.
>  The "skip the next (future) element based on the current one" is an
> inherently messy concept.

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?


[...]

>  for ( i = 0 ; i < a.size ; i += 1 )
>    puts a[i]
>    i += 1 if a[i] == "b"
>  end

You know that those are not the same. The first one is looking back,
the second could look forward.

> Out of curiousity, do you think either of these would be clearer?
>
>  a.each_with_prev do |elt, prev|
>    next if prev == "b"
>    puts elt
>  end

Definitely, but one can do this already with inject. Well, almost.

> A lot of Ruby's clarity comes not from being able to write things
> clearly in the "raw" language, but from being able to easily
> customize the language for your needs.

Of course, and that is what I am asking in the whole thread! Is there
a nicer way for this loop... Let's see. I have this each_with_next
now, but still I am completely unstatisfied with my loop. But I am now
convinced that I have the optimal solution, even if it the way it
looks now. And btw., I need a look-ahead for this specific purpose.
The each_with_previous was a very nice hint, thanks, even if I wasn't
able to use it. I'd probably have to create each_with_prev_and_next.


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


a=[:a, :b, :b, :c, :b, :d, :e]

skip=false

a.each_with_next do |elt,n|
  if skip
    skip=false
    next
  end
  if elt==:b && n==:b
    puts "double_b"
    skip=true
  else
    puts elt
  end
end


Patrick

(please, no cc:)
clr10.10.randomuser (Guest)
on 2005-12-08 14:08
(Received via mailing list)
Hi mental,

> Well, let's assume we've got persistence.  What would you expect
> each of the following to do?

[...]

ok, rethinking and withdrawing my answer....

Your examples are pretty good. I think I come to the conclusion that
it is pretty hard to find a way to manipulate the counter in a for-loop.

This would still allow something like 'skip next n elements', since we
are walking along a sequence.

Thanks for these good examples,

Patrick
mental (Guest)
on 2005-12-08 14:28
(Received via mailing list)
On Thu, 2005-12-08 at 18:07 +0900, Patrick G. wrote:
> 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...

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

-mental
blargity (Guest)
on 2005-12-08 18:06
(Received via mailing list)
On Thursday 08 December 2005 02:07, Patrick G. wrote:
> >> OK, then it makes sense that one cannot manipulate counter. But IMO it
> >> is not intuitive (POLS ;-)) that the counter in the for-loop can't be
> >> changed.
> >
> > Don't base your sense of "intuitive" on other languages.
>
> ? How else should I expect a behaviour, if not from other languages?

Perhaps how Ruby defines the construct, since it's not another language?

The general idea here is simply that the Ruby way(R) isn't about
skipping
elements when going through one at a time.  That's why this construct
doesn't
work like you're expecting, because there are more Ruby-ish ways to
accomplish it, but do please come up with an example where this is the
best
way to do it, and I'll be more than happy to calm down and stop whining
at
you. :-)

> [...]
>
> > Use RegExps to do your string processing then.
>
> Yes, but as soon as my elements aren't strings anymore, I need another
> solution.

Ok, so what are your elements? ;-)
Patrick G. (Guest)
on 2005-12-12 14:08
(Received via mailing list)
Hi mental,


[...]

Your two solutions are very nice, thank you. I think I'll go for the
each_with_next.

> What's the actual problem you're trying to solve?  I'll bet there's a
> more "outside the box" solution.

I have to print out some schedule. I create the parts by having a new
specialized class parse some xml source, do some calculation and then
output each. Let's say, one part of this schedule is a list of
members. So I have

class ListOfMembers
   def parse(src)
      # ....
   end
   def render
      # ....
   end
end

There are other items in the schedule that I need to output. I collect
all these parts in an array:

@schedule << ListOfMember.new.parse(the source)
....

and output all like

@schedule.each { |elt|  elt.render }

but in case of two 'ListOfMembers' following each other, they should be
output next to each other, so I wrap them in a 'Group of two
ListOfMembers'


This is why I want to have a lookahead and a skip_next element to skip
the second 'ListOfMembers' if the first one is already one of those
and pass both to 'render two LOM'. It would have been easy in

for i in 0...schedule.size
  if schedule[i]==LOM && schedule[i+1]==LOM
    # do the two LOM thing
    i += 1
  else
    schedule[i].render
  end
end

Your ArrayStream looks very much like that, I begin to like it!

Thanks for your answer,

Patrick
MenTaLguY (Guest)
on 2005-12-12 14:59
(Received via mailing list)
On Mon, 2005-12-12 at 21:07 +0900, Patrick G. wrote:
> Your two solutions are very nice, thank you. I think I'll go for the
> each_with_next.

Cool.  Note that (per the recent thread with Matz) I was wrong about
needing to program the iterator defensively for redo/next.  Those
confine their effects to the block, and you'd handle retry/break with
'ensure' like you would other exits (e.g. exceptions), but normally you
don't need to care.

Anyway, this is sufficient for each_with_next:

 module Enumerable
   def each_with_next(last=nil)
     prev = nil
     first = true
     each do |elt|
       if first
         first = false
         next
       else
         yield prev, elt
       end
       prev = elt
     end
     yield prev, last unless first
   end
 end

> but in case of two 'ListOfMembers' following each other, they should be
> output next to each other, so I wrap them in a 'Group of two ListOfMembers'

Hmm.  What happens if you have three in a row?

Seems to me you could do something like this:

 collapsed = []
 @schedule.each do |elt|
   prev = collapsed.last
   if LOM === prev && LOM === elt
     grp = GOLM.new
     grp.add( prev )
     grp.add( elt )
     collapsed[-1] = grp
   elsif GOLM === prev && LOM === elt
     prev.add( elt )
   else
     collapsed << elt
   end
 end
 collapsed.each { |elt| elt.render }

Lookbehind + a little mutation.  No magic iterators required...

-mental
Patrick G. (Guest)
on 2005-12-13 13:16
(Received via mailing list)
Hi mental,


[...]

>> output next to each other, so I wrap them in a 'Group of two ListOfMember=
> s'
>
> Hmm.  What happens if you have three in a row?

'this can't happen'

> Seems to me you could do something like this:
>
>  collapsed = []
>  @schedule.each do |elt|


....

yes, but this involves another step which reduces readability a bit.

Thanks again,

Patrick
This topic is locked and can not be replied to.