Forum: Ruby Re: Enumerable#collect and #select best practices

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Joe B. (Guest)
on 2006-05-16 22:29
(Received via mailing list)
-----Original Message-----
From: Daniel S. [mailto:removed_email_address@domain.invalid]

> I need to collect the results of calling a method on each object in an

> Enumerable that defines that method. Currently, I have this:

>   enum.collect{|obj| obj.a_method rescue nil }.compact

> though this could also work:

This looks like prime .inject territory

  enum.inject([]) do |l,obj|
    (l << obj.a_method) if obj.respond_to? :a_method
  end
Daniel S. (Guest)
on 2006-05-16 22:49
(Received via mailing list)
Joe B. wrote:
> This looks like prime .inject territory
>
>   enum.inject([]) do |l,obj|
>     (l << obj.a_method) if obj.respond_to? :a_method
>   end

Wonderful!

Now, what if I wanted to generalize it? That is, a method on Enumerable
  that returns an array of the results of calling a a given block with
each item in the array, excluding the items that do not respond to that
method.

This is what I've got:

   module Enumerable
     # need a better name...
     def collect_x
       collect do |obj|
         begin
           yield obj
         rescue NoMethodError
           nil
         end
       end.compact
     end
   end

Though it's still verbose...
Logan C. (Guest)
on 2006-05-17 00:45
(Received via mailing list)
On May 16, 2006, at 2:48 PM, Daniel S. wrote:

> given block with each item in the array, excluding the items that
>         rescue NoMethodError
>           nil
>         end
>       end.compact
>     end
>   end
>
> Though it's still verbose...
>

I don't think you should use #compact. What if #a_method happens to
return nil as a result? Also rescuing NoMethodError, is risky if you
have a bug inside #a_method.

I would write it:

module Enumerable
   def selective_collect(msg_name, *args)
       inject([]) do |result_list, value|
         if value.respond_to? msg_name
            result_list << value.send(msg_name, *args)
         end
         result_list
       end
   end
end

x = [ ... ]
x.selective_collect(:a_method)

of x.selective_collect(:another_method, "hello")
Daniel S. (Guest)
on 2006-05-17 02:19
(Received via mailing list)
Logan C. wrote:
>            result_list << value.send(msg_name, *args)
>         end
>         result_list
>       end
>   end
> end
>
> x = [ ... ]
> x.selective_collect(:a_method)
>
> of x.selective_collect(:another_method, "hello")

Yeah, I made a similar implementation -- it just doesn't feel Rubyish
enough not to pass a block to an iterator...

Well, I guess it's a border case; a general solution is probably not
needed.


Thanks for your response,
Daniel
YANAGAWA Kazuhisa (Guest)
on 2006-05-18 20:21
(Received via mailing list)
In Message-Id: <removed_email_address@domain.invalid>
Logan C. <removed_email_address@domain.invalid> writes:

> I don't think you should use #compact. What if #a_method happens to
> return nil as a result? Also rescuing NoMethodError, is risky if you
> have a bug inside #a_method.

And if your block given to collect is fairly complex, needless work
may affects scalability of code.  Ie. If the number of items will be
selected
are small and a source array is very huge --- say, 100 : 10,000,000
--- useless 9,999,900 invocations are avoided if "select then collect"
approach.
This topic is locked and can not be replied to.