[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Iterator Fu Failing Me

James Gray

1/7/2006 4:37:00 AM

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

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

Thanks for any tips you can offer.

James Edward Gray II



37 Answers

Marcel Molina Jr.

1/7/2006 4:39:00 AM

0

On Sat, Jan 07, 2006 at 01:36:30PM +0900, James Edward Gray II wrote:
> 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
>
> Except that I want the return result of parse?(), instead of the
> class that took it.

do/end doesn't bind tightly enough for the assignment to 'elements'. Using {}
should work.

marcel
--
Marcel Molina Jr. <marcel@vernix.org>


James Gray

1/7/2006 4:47:00 AM

0

On Jan 6, 2006, at 10:39 PM, Marcel Molina Jr. wrote:

> On Sat, Jan 07, 2006 at 01:36:30PM +0900, James Edward Gray II wrote:
>> 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.
>
> do/end doesn't bind tightly enough for the assignment to
> 'elements'. Using {}
> should work.

Sure it does:

>> numbers = (1..3).map do |n|
?> n * 2
>> end
=> [2, 4, 6]

My problem is not syntax. It's that I can't find a way to iterate to
the result of parse?().

James Edward Gray II



Michael Q. Harris

1/7/2006 5:33:00 AM

0

Are "Write a new Enumerable method that mimics find but returns the
block result instead" or "do a less elegant solution involving
assignment" not acceptable solutions?

GFunk913

1/7/2006 5:34:00 AM

0

Are "Write a new Enumerable method that mimics find but returns the
block result instead" or "do a less elegant solution involving
assignment" not acceptable solutions?

James Edward Gray II wrote:
> 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
>
> Except that I want the return result of parse?(), instead of the
> class that took it.
>
> Thanks for any tips you can offer.
>
> James Edward Gray II

Ara.T.Howard

1/7/2006 6:00:00 AM

0

ES

1/7/2006 6:16:00 AM

0

On 2006.01.07 13:36, James Edward Gray II wrote:
> 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
}
}

> Except that I want the return result of parse?(), instead of the
> class that took it.
>
> Thanks for any tips you can offer.
>
> James Edward Gray II


E


Henrik Martensson

1/7/2006 10:33:00 AM

0

On Sat, 2006-01-07 at 05:36, James Edward Gray II wrote:
> 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

I am not certain if this solution fits, but...

There is a refactoring named Replace Conditional with Polymorphism that
might solve your problem. The idea is that when you have a conditional,
you can create a class hierarchy with one subclass for each leg in the
conditional. (See
http://www.refactoring.com/catalog/replaceConditionalWithPolymor... for a slightly better explanation.)

You already have several different parser classes, and of course you
don't need to bother with a class hierarchy, so you could do something
like:

parser = ParserFactory.create(parser_type)
...
elements = tokens.map do |token|
parser.parse?(token)
end

Note that 'parser_type' may well be the class of a token in tokens.

This works assuming that all tokens in 'tokens' are of the same type.
For example, you have one parser for handling CSV data, another for XML,
a third for SGML, a fourth for non-compliant HTML, etc.

On the other hand, if each parser class handles a subset of the tokens
you get from one type of input, for example you have tokenized an XML
file, and have one parser for elements, another for processing
instructions, a third for text nodes, etc., you will need something
different. A case statement would do the trick:

elements = tokens.map do |token|
case token
when Element
ElementParser.parse?(token)
when Text
TextParser.parse?(token)
...
else
raise "Unknown token type"
end
end

You can then factor out the case statement into a method named parse?,
move the parse? method to a class of it's own, and be back to:

elements = tokens.map do |token|
ParseEverything.parse?(token)
end

A while ago I wrote a CSV parser in Java for a talk on Test Driven
Design. The Java code got horribly complex, but it struck me that in
Ruby I could do this:

class Parser
def parse(reader, writer)
tokenize(reader) { |type| |value|
write(type, value, writer)
}
end
end

By mixing in tokenize and write methods, it would be possible to build
parsers that handle most formats.

I hope this helps.

/Henrik

--

http://www.henrikmarte... - Reflections on software development





Brian Mitchell

1/7/2006 11:04:00 AM

0

On 1/6/06, James Edward Gray II <james@grayproductions.net> wrote:
> 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
>

How about:

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

It isn't perfectly efficient but it is short.

Brian.


Robert Klemme

1/7/2006 1:28:00 PM

0

James Edward Gray II <james@grayproductions.net> wrote:
> 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
>
> Except that I want the return result of parse?(), instead of the
> class that took it.
>
> Thanks for any tips you can offer.
>
> James Edward Gray II

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

alternative using a method definition

def parse(token)
parsers.each {|par| a=par.parse(token) and return a}
false
end

But the best solution is

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

:-)

Kind regards

robert

Ara.T.Howard

1/7/2006 2:49:00 PM

0