Cleaner way to do this?

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

> 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.

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

···

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.

Hi --

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 :slight_smile:
There's no technical reason for it not to, but I think it's kind of
unidiomatic.

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.

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}" })

David

···

On Fri, 5 Nov 2004, Mark Hubbart wrote:

--
David A. Black
dblack@wobblini.net

Hi,

Hi --

> 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 :slight_smile:
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 :slight_smile: 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
:slight_smile:

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

cheers,
Mark

···

On Fri, 5 Nov 2004 23:05:36 +0900, David A. Black <dblack@wobblini.net> wrote:

On Fri, 5 Nov 2004, Mark Hubbart wrote:

David

--
David A. Black
dblack@wobblini.net