[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Re: Cleaner way to do this?

Harry Ohlsen

11/5/2004 3:34:00 AM

> From: Zach Dennis [mailto:zdennis@mktec.com]
> Sent: Friday, 5 November 2004 14:01
> Subject: Re: Cleaner way to do this?

> If your friend knows the processing he wants to do on the first >
element, why not just keep it simple...

> arr = [1,2,3,4,5]
> process = proc{ |e| print e }.call( arr[0] )
> arr[1...arr.length].each do |e|
> print ", #{e}"
> end

Agreed :-).

I think the original discussion came about because he was interested in
iterators and wanted to understand how one might write an efficient
iterator that allowed for handling of special cases, like doing
something different with the first or last entry in a collection.

I didn't like to dampen that kind of enthusiasm.

H.


************************************************************************

If you have received this e-mail in error, please delete it and notify the sender as soon as possible. The contents of this e-mail may be confidential and the unauthorized use, copying, or dissemination of it and any attachments to it, is prohibited.

Internet communications are not secure and Hyperion does not, therefore, accept legal responsibility for the contents of this message nor for any damage caused by viruses. The views expressed here do not necessarily represent those of Hyperion.

For more information about Hyperion, please visit our Web site at www.hyperion.com





4 Answers

Brian Candler

11/5/2004 9:19:00 AM

0

> > If your friend knows the processing he wants to do on the first
> > element, why not just keep it simple...
>
> > arr = [1,2,3,4,5]
> > process = proc{ |e| print e }.call( arr[0] )
> > arr[1...arr.length].each do |e|
> > print ", #{e}"
> > end
>
> Agreed :-).
>
> I think the original discussion came about because he was interested in
> iterators and wanted to understand how one might write an efficient
> iterator that allowed for handling of special cases, like doing
> something different with the first or last entry in a collection.
>
> I didn't like to dampen that kind of enthusiasm.

A generic solution, which works on anything enumerable (such as lines of a
File) will not use foo.length, because the number of items isn't necessarily
known at the start.

Here's one way to avoid the test each time round the loop:

module Enumerable
def each_except_first(first=nil, &rest)
iter = proc { |e| first.call(e) if first; iter = rest }
each { |e| iter.call(e) }
end
end

#a = File.open("/etc/motd")
a = ["one", "two", "three"]
a.each_except_first(proc { |e| print e }) { |e| print ", #{e}" }

However this still involves an extra level of block call. It's a shame
there's not a Proc#replace method, otherwise you could write something like

module Enumerable
def each_except_first(first=nil, &rest)
iter = proc { |e| first.call(e) if first; iter.replace(rest) }
each &iter
end
end

Regards,

Brian.


Mark Hubbart

11/5/2004 11:49:00 AM

0

On Fri, 5 Nov 2004 12:34:12 +0900, Harry Ohlsen
<harry_ohlsen@hyperion.com> wrote:
> > From: Zach Dennis [mailto:zdennis@mktec.com]
> > Sent: Friday, 5 November 2004 14:01
>
>
> > Subject: Re: Cleaner way to do this?
>
> > If your friend knows the processing he wants to do on the first >
> element, why not just keep it simple...
>
> > arr = [1,2,3,4,5]
> > process = proc{ |e| print e }.call( arr[0] )
> > arr[1...arr.length].each do |e|
> > print ", #{e}"
> > end
>
> Agreed :-).
>
> I think the original discussion came about because he was interested in
> iterators and wanted to understand how one might write an efficient
> iterator that allowed for handling of special cases, like doing
> something different with the first or last entry in a collection.
>
> I didn't like to dampen that kind of enthusiasm.

Well, after some interesting sidetracked thoughts, I came up with this form:

[1,2,3,4].do_first{|n| print n}.then_rest{|n| print ", ",n}
1, 2, 3, 4 ==>[1, 2, 3, 4]

Then this quickly hacked implementation, using singleton methods to
turn the first block into a proxy quasi-enumerable object.

module Enumerable
def do_first(&block)
class << block
def then_rest
first_done = nil
@enum.each do |arg|
yield arg if first_done
first_done ||= [self[arg]]
end
end
end
this = self
block.instance_eval{@enum = this}
block
end
end

I'm sorta perversely proud of this code. I shouldn't be, but I think I
am. I almost didn't post this, but I just had to...

A proper implementation might be possible using a special
ProxyEnumerable class, which delegates to the real enumerable that you
pass it on initialization.

cheers,
Mark


dblack

11/5/2004 2:06:00 PM

0

Mark Hubbart

11/5/2004 7:40:00 PM

0

Hi,

On Fri, 5 Nov 2004 23:05:36 +0900, David A. Black <dblack@wobblini.net> wrote:
> Hi --
>
> On Fri, 5 Nov 2004, Mark Hubbart wrote:
>
> > Well, after some interesting sidetracked thoughts, I came up with this form:
> >
> > [1,2,3,4].do_first{|n| print n}.then_rest{|n| print ", ",n}
> > 1, 2, 3, 4 ==>[1, 2, 3, 4]
> >
> > Then this quickly hacked implementation, using singleton methods to
> > turn the first block into a proxy quasi-enumerable object.
>
> Hmmmm.... it's a bit of a stretch to "get" that the first call is
> going to return its own block (with or without modifications :-)
> There's no technical reason for it not to, but I think it's kind of
> unidiomatic.

I'm not claiming this is good code - I know it's not. But it was fun
to write :) I think it shows how flexible Ruby is. The end syntax is
very readable, though the method chaining isn't. And the method names
leave something to be desired.

> > module Enumerable
> > def do_first(&block)
> > class << block
> > def then_rest
> > first_done = nil
> > @enum.each do |arg|
> > yield arg if first_done
> > first_done ||= [self[arg]]
> > end
> > end
> > end
> > this = self
> > block.instance_eval{@enum = this}
> > block
> > end
> > end
>
> Don't forget that there's no Enumerable#[], so this would not work for
> all Enumerables.

I didn't use Enumerable#[]

"one\ntwo\nthree".do_first{|n| print n.chomp}.then_rest{|n| print ", ",n.chomp}
one, two, three ==>"one\ntwo\nthree"
(1..3).do_first{|n| print n}.then_rest{|n| print ", ",n}
1, 2, 3 ==>1..3

I did use #[] on a block...

> For what it's worth (maybe not much), here's another possible way to
> go about it:
>
> module M
> def do_both(b1, b2, n=1)
> current = b1
> i = 1
> each do |e|
> current.call(e)
> i += 1
> current = b2 if i > n
> end
> end
> end
>
> arr = [1,2,3,4,5] .extend(M)
> arr.do_both(lambda {|x| print x }, lambda {|x| print ", #{x}" })

The challenge I took was to figure out a way to pass two blocks,
without lambda/proc/Proc.new. I did that by using method chaining and
a proxy object. I agree that the idea wasn't worth much, except maybe
as an exercise. And I'm not suggesting the OP show this to his friend
:)

Anyway, please forgive my ugly code :) I'll leave this now...

cheers,
Mark

>
> David
>
> --
> David A. Black
> dblack@wobblini.net
>
>