[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Ruby way to take some action only once in a loop?

Evgeniy Dolzhenko

10/21/2008 10:08:00 AM

This example is slightly contrived, but I hope it does illustrate the question.

Say you're generating table from an array and want to output header row
only if there will be actual rows in the table.

Basically I would like something like this:


rows = ["row1", "row2"]

rows.each do |row|
do_once do
write(header) # this block gets executed only once for whole loop
end

write(row)
end


I know, it's very questionable from the "best practices" point of view
as it breaks execution sequence, but it would be interesting to see
possible solutions.

Here is dumb one which relies on Proc#binding for keeping flags about
executed blocks and Kernel#caller for identifying them:


def do_once(&block)
executed_flag_name = "__#{caller[0].gsub(/\W/, "_")}_executed"
if (eval(executed_flag_name, block) rescue nil) # works only with
non-scoped "for" loops
return
else
eval("#{executed_flag_name} = true", block)
yield
end
end

7 Answers

Nobuyoshi Nakada

10/21/2008 11:39:00 AM

0

Hi,

At Tue, 21 Oct 2008 19:07:46 +0900,
Evgeniy Dolzhenko wrote in [ruby-talk:318215]:
> rows = ["row1", "row2"]
>
> rows.each do |row|
if (first=true)..false and first
> write(header) # this block gets executed only once for whole loop
> end
>
> write(row)
> end

--
Nobu Nakada

Robert Klemme

10/21/2008 11:49:00 AM

0

2008/10/21 Nobuyoshi Nakada <nobu@ruby-lang.org>:
> At Tue, 21 Oct 2008 19:07:46 +0900,
> Evgeniy Dolzhenko wrote in [ruby-talk:318215]:
>> rows = ["row1", "row2"]
>>
>> rows.each do |row|
> if (first=true)..false and first

Amazing!

>> write(header) # this block gets executed only once for whole loop
>> end

Here's what I'd probably do

unless rows.empty?
write header

rows.each do |rw|
write row
end
end

Or

rows.each_with_index |row, index|
write header if index == 0
write row
end

Kind regards

robert

--
remember.guy do |as, often| as.you_can - without end

Jean-François Trân

10/21/2008 12:56:00 PM

0

2008/10/21 Robert Klemme <shortcutter@googlemail.com>:
>>> rows =3D ["row1", "row2"]
>>>
>>> rows.each do |row|
>> if (first=3Dtrue)..false and first
>
> Amazing!

This is awesome, but I don't get all

if I write :
foo =3D (first=3Dtrue)..false

I've got a 'bad value for Range", but it works in a condition for if.

first is a local variable the first time in the loop but not after, I
don't understand why (the assignment first =3D true is not executed
each time ?).

very cute but puzzling.

-- Jean-Fran=E7ois.

--=20
Les 50 ans du Lisp : http://www....
http://twitter.com/...

Lloyd Linklater

10/21/2008 1:06:00 PM

0

You do not say if the "do once" code must rely on something obtained in
the loop. If you will excuse me for a simplistic question, if you are
doing something like writing a header, why not do it before you even
start the loop? I dislike checking for a conditional every time a loop
runs. It sucks up CPU time unnecessarily.
--
Posted via http://www.ruby-....

list. rb

10/21/2008 1:25:00 PM

0

[Note: parts of this message were removed to make it a legal post.]

On Tue, Oct 21, 2008 at 9:05 AM, Lloyd Linklater <lloyd@2live4.com> wrote:

> You do not say if the "do once" code must rely on something obtained in
> the loop. If you will excuse me for a simplistic question, if you are
> doing something like writing a header, why not do it before you even
> start the loop? I dislike checking for a conditional every time a loop
> runs. It sucks up CPU time unnecessarily.
> --
> Posted via http://www.ruby-....
>
>
1.times {
puts 'hi'
}

Evgeniy Dolzhenko

10/21/2008 3:34:00 PM

0

The most embarrassing fact about Nobu's solution is its all in Pickaxe
http://www.rubycentral.com/pickaxe/tut_expres... :)

Exactly what happens is described in this quote: "You can use a Ruby range
as a boolean expression. A range such as exp1..exp2 will evaluate
as false until exp1 becomes true. The range will then evaluate
as true until exp2 becomes true. Once this happens, the range resets,
ready to fire again".

So exp1 (first=true) evaluates to true, then interpreter evaluates
boolean range
to true and waits until exp2 (false) becomes true which will never happen,
but on all consequent runs "and first" will guard "if" body from executing
(because exp1 is not executed anymore "first" gives you nil,
AFAIK what happens here is that "first" becomes "defined" name for the
interpreter
after first assignment but I can't say I have good grasp on this
"scoping" feature).

The problem with Nobu's solution obviously is inverse to mine (not working with
"for" loops).

Re Robert Klemme: as I said before this example is contrived, think about
cases where you don't how many iteration there will be until you start
iterating.

Enumerable#each_with_index is cool, and you get it for free by defining "each"
method on your class, but in my real-world case iterator isn't called each.

Re Jean-Francois Tran: seems like boolean range is completely
different beast from
Range object

Re Lloyd Linklater: I don't remember when was the last time when I was
battling CPU
time issues (IO, DB - there are plenty of), moreover check for the boolean flag
should be lightning fast (at least in ideal world :) ).
So it just seems "nice and clean" to me to keep all stuff (table
generating in this case)
in one continuous block.

Re list. rb: now that's refreshing :)

Jean-François Trân

10/21/2008 4:13:00 PM

0

2008/10/21 Evgeniy Dolzhenko <dolzenko@gmail.com>:

> The most embarrassing fact about Nobu's solution is its all in Pickaxe
> http://www.rubycentral.com/pickaxe/tut_expres... :)
>
> Exactly what happens is described in this quote: "You can use a Ruby rang=
e
> as a boolean expression. A range such as exp1..exp2 will evaluate
> as false until exp1 becomes true. The range will then evaluate
> as true until exp2 becomes true. Once this happens, the range resets,
> ready to fire again".
>
> So exp1 (first=3Dtrue) evaluates to true, then interpreter evaluates
> boolean range
> to true and waits until exp2 (false) becomes true which will never happen=
,
> but on all consequent runs "and first" will guard "if" body from executin=
g

thanks for the explanations.

> (because exp1 is not executed anymore "first" gives you nil,
> AFAIK what happens here is that "first" becomes "defined" name for the
> interpreter
> after first assignment but I can't say I have good grasp on this
> "scoping" feature).

I guess it can be explained because at parse time, Ruby has seen
first assignment in the condition, and then considered 'first' as a variabl=
e
even if the assignment is not executed.

it's also in the Pickaxe :)

http://www.rubycentral.com/pickaxe/lan...
chapter : Variable/Method Ambiguity

-- Jean-Fran=E7ois.

--=20
Les 50 ans du Lisp : http://www....
http://twitter.com/...