tags:

views:

2401

answers:

8

To do the equivalent of Python list comprehensions, I'm doing the following:

some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}

Is there a better way to do this...perhaps with one method call?

+18  A: 

If you really want to, you can create an Array#comprehend method like this:

class Array
  def comprehend(&block)
    return self if block.nil?
    self.collect(&block).compact
  end
end

some_array = [1, 2, 3, 4, 5, 6]
new_array = some_array.comprehend {|x| x * 3 if x % 2 == 0}
puts new_array

Prints:

6
12
18

I would probably just do it the way you did though.

Robert Gamble
+10  A: 

How 'bout:

some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact

Slightly cleaner, at least to my taste, and according to a quick benchmark test about 15% faster than your version...

glenn mcdonald
some_array.map {|x| x * 3 if x % 2 == 0}.compact works too
Hafthor
+1  A: 

I think the most list comprehension-esque would be the following:

some_array.select{ |x| x * 3 if x % 2 == 0 }

Since Ruby allows us to place the conditional after the expression, we get syntax similar to the Python version of the list comprehension. Also, since the select method does not include anything that equates to false, all nil values are removed from the resultant list and no call to compact is necessary as would be the case if we had used map or collect instead.

Christopher Roach
This doesn't appear to work. At least in Ruby 1.8.6, [1,2,3,4,5,6].select {|x| x * 3 if x % 2 == 0}evaluates to [2, 4, 6]Enumerable#select only cares about whether the block evaluates to true or false, not what value it outputs, AFAIK.
Greg Campbell
+2  A: 

alternative solution that will work in every implementation and run in O(n) insted of O(2n) time.

some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
Pedro Morte Rolo
You mean it traverses the list only once. If you go by the formal definition, O(n) equals O(2n). Just nitpicking :)
Daniel Hepper
@Daniel Harper :) Not only you are right, but also for the average case, transversing the list once to discard some entries, and then again to perform an operation can be acctually better in the average cases :)
Pedro Morte Rolo
+1  A: 

[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact

=> [6, 12, 18]

That works for me. It is also clean. Yes, it's the same as .map, but I think collect makes the code more understandable.

Sorenly
+1  A: 

Like Pedro mentioned, you can fuse together the chained calls to Enumerable#select and Enumerable#map, avoiding a traversal over the selected elements. This is true because Enumerable#select is a specialization of fold (or #inject). I posted a hasty introduction to the topic at the Ruby subreddit.

Manually fusing Array transformations can be tedious, so maybe someone could play with Robert Gamble's #comprehend implementation to make this select/map pattern prettier.

jvoorhis
A: 
anoiaque
+1  A: 

I discussed this topic with Rein Henrichs, who tells me that the best performing solution is map { ... }.compact. This makes good sense because it avoids building intermediate Arrays (as with the immutable usage of Enumerable#inject), and it avoids growing the Array, which causes allocation. It's as general as any of the others unless your collection can contain nil elements.

I haven't compared this with .select {...}.map{...}. It's possible that Ruby's C implementation of Enumerable#select is very good also.

jvoorhis