[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Re: Jumping to "the next one" in something#each

Philip Hallstrom

2/9/2007 10:50:00 PM

4 Answers

Gary Wright

2/10/2007 12:46:00 AM

0


On Feb 9, 2007, at 6:23 PM, Garance A Drosehn wrote:
> I should also say that I am not looking for a quick-fix for some
> specific script that I am writing. This is more of a "blue-sky"
> topic,
> where I am thinking that maybe some future version of ruby could
> introduce a new feature for these situations.

I think that using an array as a queue for processing items is
pretty general. The idea of pushing things back into a queue is
also a reasonably common pattern. You suggested that you didn't
want to change your array during iteration, but if you simply
think of the array as a queue of items then it certainly makes
sense to alter the queue as you process items. You can always dup
the original array if you need continued access to the original
collection.

I gave an illustration with ARGV but the pattern works for any type
of a collection. You can always use Enumerable#to_a to convert
something to an array:

hash = { 1 => 2, 3=>4 }
pairs = hash.to_a

while pair = pairs.shift
# the next pair is available as pairs[0], if necessary
end

Is there some particular reason you prefer iteration via #each over
a while loop? It seems like the problem you described is more general
than the type of iteration provided by #each and so it needs a more
general solution (like a while loop). As you've said, you've managed
to shoehorn in a solution with #each via flags and such, but
perhaps that indicates that #each is simply the wrong pattern for your
use cases.

It isn't entirely clear to me from the single example what 'pattern'
you are trying to implement. Perhaps posting another use
case would generate some more suggestions.


Gary Wright




Logan Capaldo

2/10/2007 12:56:00 AM

0

On Sat, Feb 10, 2007 at 08:23:23AM +0900, Garance A Drosehn wrote:
> On 2/9/07, Garance A Drosehn <drosihn@gmail.com> wrote:
> >
> >
> >Besides, I wanted something which works for *any* routine which
> >takes a code-block, not just objects of type Array. How could you
> >make that work for the each_pair method of Hash objects?
> >
> >I was thinking there should be some "big-picture" solution possible,
> >one which would work for all methods which take a code-block
> >parameter. That is probably too complicated a goal, but at least a
> >solution which would work for some large subset of those methods.
>
>
> I should also say that I am not looking for a quick-fix for some
> specific script that I am writing. This is more of a "blue-sky" topic,
> where I am thinking that maybe some future version of ruby could
> introduce a new feature for these situations.
>
> I think the kind of solution that I'm looking for would require a change
> to ruby itself. However, I also realize that I may have missed some
> features which are already in ruby, and which already do exactly what
> I want.
>
require 'generator'
require 'enumerator'

g = Generator.new([1,2,3])

until g.end?
p g.next
end

def happy
yield 1
yield 2
yield 3
end

g2 = Generator.new(Enumerable::Enumerator.new(self, :happy))

until g2.end
p g2.next
end

> --
> Garance Alistair Drosehn = drosihn@gmail.com
> Senior Systems Programmer
> Rensselaer Polytechnic Institute; Troy, NY; USA

Brian Candler

2/10/2007 2:53:00 PM

0

On Sat, Feb 10, 2007 at 08:11:16AM +0900, Garance A Drosehn wrote:
> >Well, if you don't mind trashing the array you're processing you could use
> >Array.shift to remove the first element and return it... then just
> >re-order your loop to exit once the element returns is nil.
>
>
> That's not a general solution, though. Consider an example of:
>
> filetree.added_files.keys.sort.each { |fname|
> ...whatever...
> }
>
> I'd have to create a temp-array to hold the result from the sort step.

Note that Array#sort *does* always generate a temp array.

(a.sort is implemented internally as a.dup.sort!)

> Besides, I wanted something which works for *any* routine which
> takes a code-block, not just objects of type Array. How could you
> make that work for the each_pair method of Hash objects?

Change h.each_pair to h.to_a (which also creates a temp array, of course)

In the most general case, you might need to explicitly construct the array,
changing
foo.each { |x,y,z| ... }
to something like
foo.map { |*x| x }

But setting this aside: I think it *is* possible to get the pattern you
asked for - i.e. being able to 'pop' the next value from an enumerable on
demand, without first flattening it into an array. Have a look at the
Generator class:

require 'generator'

a = ["arg", "-f", "one", "-c", "two", "final"]

g = Generator.new(a)
while g.next?
arg = g.next
if arg == "-f"
file = g.next
puts "File: #{file}"
elsif arg == "-c"
conf = g.next
puts "Conf: #{conf}"
else
puts "Arg: #{arg}"
end
end

Generators are implemented using continuations, which are not very
efficient. So you sacrifice speed for convenience.

> And to complicate things a bit more, sometimes the option might want
> to *check* the next option, but not necessarily use it up. For instance,
> if the "-f" option can take one-or-more names, then it would keep taking
> the next value for 'arg' until it sees arg =~ /^-/

Your generator then needs some buffering to be able to fetch and remember
the next value. But we already have this; Generator#current tells you the
'next' item without actually removing it.

So try this for size:

require 'generator'

module Enumerable
def geneach
g = Generator.new(self)
while g.next?
yield g, g.next
end
end
end

a = ["arg", "-f", "one", "two", "three", "-c", "conffile", "final"]

a.geneach do |g, arg|
if arg == "-f"
while g.current !~ /^-/
file = g.next
puts "File: #{file}"
end
elsif arg == "-c"
conf = g.next
puts "Conf: #{conf}"
else
puts "Arg: #{arg}"
end
end

There are probably some rough edges to sort out in this pattern (esp. what
happens if the enumerator yields multiple values) but you get the idea.

Now, doing all this with generators is a general-purpose solution; it works
with any Enumerable (i.e. anything which implements the 'each' method), but
as I said before, it's inefficient. You can optimise this by making a custom
geneach method for Arrays, which will be much faster.

This gives:

class ArrayGenerator
def initialize(a)
@a = a
@index = 0
end
def next?
@index < @a.size
end
def next
v = @a[@index]
@index += 1
v
end
def current
@a[@index]
end
#can also add other methods to match Generator, e.g.
#def rewind
# @index = 0
#end
end

class Array
def geneach
g = ArrayGenerator.new(self)
while g.next?
yield g, g.next
end
end
end

a = ["arg", "-f", "one", "two", "three", "-c", "conffile", "final"]

a.geneach do |g, arg|
if arg == "-f"
while g.current !~ /^-/
file = g.next
puts "File: #{file}"
end
elsif arg == "-c"
conf = g.next
puts "Conf: #{conf}"
else
puts "Arg: #{arg}"
end
end

Notice that the 'main' program which parses the array is unchanged.

You can of course have both implementations loaded at once. So Arrays will
get the efficient behaviour, and anything else that is Enumerable will get
the continuation implementation.

HTH,

B.

Ara.T.Howard

2/10/2007 6:20:00 PM

0