Iterator Fu Failing Me

  • Thread starter James Edward Gray II
  • Start date
B

Bob Hutchison

Bob Hutchison said:
On Jan 6, 2006, at 11:36 PM, James Edward Gray II wrote:

That works. Now can anyone give me a version of the middle section
that doesn't require I call parse?() 50 times? I want something
close to:

elements = tokens.map do |token|
[ClassA, ClassB, ClassC].find { |kind| kind.parse?(token) }
end

Except that I want the return result of parse?(), instead of the
class that took it.

Why not:

elements = tokens.map do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| result = kind.parse?
(token) }
end

Because this won't return the proper value from the block. You
need at least to add a line with "result" after the #find. :)

cut'n'past-o... oops
And then it's much more inelegant than using #detect. :)

You mean #detect or #select?

elements = tokens.select do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| results << kind.parse?
(token) }
result
end

And this is kind of handy too...

elements = tokens.map do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| result = [token,
kind.parse?(token)] }
result
end

That's wrong! That's twice I've done that. I'm going to go have a nap
now.

elements = tokens.map do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| result = [token, r =
kind.parse?(token)]; r }
result
end

Anyway, I like this version better...

elements = tokens.map do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| (result = [token,
kind.parse?(token)]).last }
result
end

or this...

elements = tokens.map do |token|
result = nil
[ClassA, ClassB, ClassC].find do |kind|
if r = kind.parse?(token) then result = [r, kind, token] end
end
result || [nil, nil, token]
end

and you can use "result = (r = kind.parse?(token)) && [r, kind,
token]" if you choose


Cheers,
Bob
----
Bob Hutchison -- blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc. -- <http://www.recursive.ca/>
Raconteur -- <http://www.raconteur.info/>
xampl for Ruby -- <http://rubyforge.org/projects/
xampl/>

----
Bob Hutchison -- blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc. -- <http://www.recursive.ca/>
Raconteur -- <http://www.raconteur.info/>
xampl for Ruby -- <http://rubyforge.org/projects/xampl/>
 
B

Bob Hutchison

You mean #detect or #select?

elements = tokens.select do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| results << kind.parse?
(token) }
result
end

This is wrong too! It should be:

elements = tokens.select do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| result = kind.parse?(token) }
result
end

Now, before I dig anymore holes, I'll stop now.

----
Bob Hutchison -- blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc. -- <http://www.recursive.ca/>
Raconteur -- <http://www.raconteur.info/>
xampl for Ruby -- <http://rubyforge.org/projects/xampl/>
 
M

Mauricio Fernandez

Bob Hutchison said:
That works. Now can anyone give me a version of the middle section
that doesn't require I call parse?() 50 times? I want something
close to:

elements = tokens.map do |token|
[ClassA, ClassB, ClassC].find { |kind| kind.parse?(token) }
end

Except that I want the return result of parse?(), instead of the
class that took it.

Why not:

elements = tokens.map do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| result = kind.parse?(token) }
end

Because this won't return the proper value from the block. You need at
least to add a line with "result" after the #find. :) And then it's much
more inelegant than using #detect. :)

Actually,

enum.c:
rb_define_method(rb_mEnumerable,"find", enum_find, -1);
rb_define_method(rb_mEnumerable,"detect", enum_find, -1);

...

(1..10).detect{|x| 10*x > 50} # => 6

...
 
J

James Edward Gray II

elements = tokens.map do |tok|
parsers.inject(false) {|a,par| a=par.parse(tok) and break a}
end

That's what I was looking for. Thank you.
But the best solution is

elements = tokens.map do |tok|
parsers.detect {|par| par.parse(tok)}
end

That's not equivalent. It's actually the same example I showed in my
original question message that didn't work. See that post for
details, but the short story is that it finds the parsers that did
the conversion, not the parsed elements I was looking for.

James Edward Gray II
 
B

Bob Hutchison

You mean #detect or #select?

#detect - because it stops as soon as it has a hit while #select
will return an array. We need just the first hit.
[nil,nil,nil,"w",2,3].find {|x|puts x;x}
nil
nil
nil
w
=> "w"
[nil,nil,nil,"w",2,3].select {|x|puts x;x}
nil
nil
nil
w
2
3
=> ["w", 2, 3]

The trouble is that detect returns the 'x' not the result of
processing x successfully (or, in terms of the original post, the
class that accepts the token not the result of the #parse? method).
That's why you have to capture the result in the block. Map returns
the result of processing.
elements = tokens.select do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| results << kind.parse?
(token) }
result
end

You don't define results and you never assign result another value
than nil...

Sorry, I've 'corrected' myself in a mail I just sent out. (My email
is suffering long delays today, and my mind isn't working all that
quickly either I think)

This is what I had intended with the "<< results" is:

results = []
tokens.each do |token|
# use find over the collection of classes to find the one that
successfully parses the
# token. Find will return the class, not the result of #parse?, so
capture that result
# by using a closure on result (Note: result must appear outside
of and before the block.
# Collect each result in results.
result = nil
[ClassA, ClassB, ClassC].find { |kind| result = kind.parse?(token)}
results << result if result
end

If you also want the nil results then you can use:

elements = tokens.map do |token|
result = nil
[ClassA, ClassB, ClassC].find { |kind| result = kind.parse?(token)}
result
end

Say what you want, find/detect is the most elegant solution.

Only they return the wrong things.

----
Bob Hutchison -- blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc. -- <http://www.recursive.ca/>
Raconteur -- <http://www.raconteur.info/>
xampl for Ruby -- <http://rubyforge.org/projects/xampl/>
 
E

Eero Saynatkari

I have a group of classes, all implementing a parse?() class method.
Calling parse?(token) will return the constructed object, if it can
be parsed by this class, or false otherwise.

I want to run a bunch of tokens through these classes, grabbing the
first fit. For example:

elements = tokens.map do |token|
ClassA.parse?(token) or
ClassB.parse?(token) or
ClassC.parse?(token)
end

That works. Now can anyone give me a version of the middle section
that doesn't require I call parse?() 50 times? I want something
close to:

elements = tokens.map do |token|
[ClassA, ClassB, ClassC].find { |kind| kind.parse?(token) }
end

# Not tested
elements = tokens.map {|token|
[A, B, C].each {|kind|
result = kind.parse?(token) and break result
}
}

fails when no kind parses. in this case the result will be

[[A,B,C]] (the return of each...)

You are (of course) absolutely right, it should be 'return result'
coupled with a default nil/false at the end.

One of these days I will learn not to write mail at four in the
morning :)
regards.
-a


E
 
R

Robert Klemme

Bob Hutchison said:
You mean #detect or #select?

#detect - because it stops as soon as it has a hit while #select
will return an array. We need just the first hit.
[nil,nil,nil,"w",2,3].find {|x|puts x;x}
nil
nil
nil
w
=> "w"
[nil,nil,nil,"w",2,3].select {|x|puts x;x}
nil
nil
nil
w
2
3
=> ["w", 2, 3]

The trouble is that detect returns the 'x' not the result of
processing x successfully (or, in terms of the original post, the
class that accepts the token not the result of the #parse? method).
That's why you have to capture the result in the block. Map returns
the result of processing.

Aargh! I shoul've tested more carefully. Yes you're completely right. But
the inject version still works, I think that's then my favourite.

elements = tokens.map do |tok|
parsers.inject(false) {|a,pars| a = pars.parse? tok and break a}
end

Thanks for correcting me!

robert
 
D

dblack

Hi --

yes. just symetry/clarity. is use this pattern alot. even if someone
doesn't understand the mechanism i generally assume the 'ClassMethods' and
'InstanceMethods' names will give it away.

Wouldn't it be clearer just to write instance methods for a module?
It feels a bit like you're creating a kind of second skin for a system
that already does this.


David

--
David A. Black
(e-mail address removed)

"Ruby for Rails", from Manning Publications, coming April 2006!
http://www.manning.com/books/black
 
B

Bob Hutchison

Bob Hutchison said:
You mean #detect or #select?

#detect - because it stops as soon as it has a hit while #select
will return an array. We need just the first hit.

[nil,nil,nil,"w",2,3].find {|x|puts x;x}
nil
nil
nil
w
=> "w"
[nil,nil,nil,"w",2,3].select {|x|puts x;x}
nil
nil
nil
w
2
3
=> ["w", 2, 3]

The trouble is that detect returns the 'x' not the result of
processing x successfully (or, in terms of the original post, the
class that accepts the token not the result of the #parse? method).
That's why you have to capture the result in the block. Map returns
the result of processing.

Aargh! I shoul've tested more carefully. Yes you're completely
right. But the inject version still works, I think that's then my
favourite.

elements = tokens.map do |tok|
parsers.inject(false) {|a,pars| a = pars.parse? tok and break a}
end

Personally, I don't like the break. It'd be very nice if there were
equivalent operations returning the result of the calculation rather
than the object.
Thanks for correcting me!

Sorry for the brutally finger-tied attempts at communication :)

----
Bob Hutchison -- blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc. -- <http://www.recursive.ca/>
Raconteur -- <http://www.raconteur.info/>
xampl for Ruby -- <http://rubyforge.org/projects/xampl/>
 
J

jwesley

Perhaps I'm missing the point, but wouldn't the following work: (?)

elements = tokens.map do |token|
val = nil
[ClassA, ClassB, ClassC].find { |kind| val = kind.parse?(token) }
val
end

Justin
 
J

James Edward Gray II

Perhaps I'm missing the point, but wouldn't the following work: (?)

elements = tokens.map do |token|
val = nil
[ClassA, ClassB, ClassC].find { |kind| val = kind.parse?(token) }
val
end

It works just fine, yes. Just doesn't feel very Rubyish to me.
<shrugs>

James Edward Gray II
 
R

Ross Bamford

Perhaps I'm missing the point, but wouldn't the following work: (?)

elements = tokens.map do |token|
val = nil
[ClassA, ClassB, ClassC].find { |kind| val = kind.parse?(token) }
val
end

It works just fine, yes. Just doesn't feel very Rubyish to me. <shrugs>

Not sure this is more Rubyish (seems a bit line-noisy) but:

class Parser
class << self
def parse?(tok)
tok.downcase if tok == self.name
end
end
end

class One < Parser; end
class Two < Parser; end
class Three < Parser; end

parsers = [One, Two, Three]
tokens = ['One','Two','One','siz','Four','Two']

r = tokens.inject [] do |r,token|
r << (parsers.zip([token] * parsers.length).detect { |p,tok| p.parse?
tok } || [])[0]
end

p r

Seems to work. It's *terribly* inefficient though I guess.
 
R

Ross Bamford

Perhaps I'm missing the point, but wouldn't the following work: (?)

elements = tokens.map do |token|
val = nil
[ClassA, ClassB, ClassC].find { |kind| val = kind.parse?(token) }
val
end

It works just fine, yes. Just doesn't feel very Rubyish to me.
<shrugs>

Not sure this is more Rubyish (seems a bit line-noisy) but:

This one looks worse, but at least really does seem to work this time:

r = tokens.inject([]) do |arr,token|
arr << parsers.zip([token]*parsers.length).inject(nil) do |out,pt|
out or pt[0].parse?(pt[1])
end
end

slotted into the code before and it gives:

["one", "two", "one", nil, nil, "three"]

Sorry about that.
 
R

Ross Bamford

r = tokens.inject([]) do |arr,token|
arr << parsers.zip([token]*parsers.length).inject(nil) do |out,pt|
out or pt[0].parse?(pt[1])
end
end

Damn it...

==

r = tokens.inject([]) do |arr,token|
arr << parsers.inject(nil) { |out,p| out or p.parse?(token) }
end

Now I'm really done. Promise.
 
B

Bob Hutchison

r = tokens.inject([]) do |arr,token|
arr << parsers.zip([token]*parsers.length).inject(nil) do |out,pt|
out or pt[0].parse?(pt[1])
end
end

Damn it...

==

r = tokens.inject([]) do |arr,token|
arr << parsers.inject(nil) { |out,p| out or p.parse?(token) }
end

Now I'm really done. Promise.

This thread is jinxed. :)

----
Bob Hutchison -- blogs at <http://www.recursive.ca/
hutch/>
Recursive Design Inc. -- <http://www.recursive.ca/>
Raconteur -- <http://www.raconteur.info/>
xampl for Ruby -- <http://rubyforge.org/projects/xampl/>
 
A

Adam Shelly

why not write a new iterator: it will probably be useful again sometime:

module Enumerable
def find_first_result
each {|item| result =3D yield item; return result if result }
nil
end
end

irb(main):075:0> tokens =3D %w( adam codes in ruby )
=3D> ["adam", "codes", "in", "ruby"]
irb(main):076:0> classes =3D [/a/, /b/, /c/]
=3D> [/a/, /b/, /c/]
irb(main):077:0> elements =3D tokens.map do |token|
irb(main):078:1* classes.find_first_result {|kind| kind =3D~ token}
irb(main):079:1> end
=3D> [0, 0, nil, 2]


-Adam
 
A

ara.t.howard

Wouldn't it be clearer just to write instance methods for a module? It
feels a bit like you're creating a kind of second skin for a system that
already does this.

generally i do just this. however, i've found it extremely useful to be able
to do


include SomeModule::InstanceMethods # mixin only instance methods

extend SomeModule::ClassMethods # mixin only class methods

include SomeModule # mixin InstanceMethods and extend with ClassMethods via
# over-ridden self::included


when factoring out code in large systems.

regards.

-a
--
===============================================================================
| ara [dot] t [dot] howard [at] noaa [dot] gov
| strong and healthy,
| who thinks of sickness until it strikes like lightning?
| preoccupied with the world,
| who thinks of death, until it arrives like thunder?
| -- milarepa
===============================================================================
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,201
Messages
2,571,049
Members
47,655
Latest member
eizareri

Latest Threads

Top