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?
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?
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.
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...
I made a quick benchmark comparing the three alternatives and map-compact really seems to be the best option.
require 'test_helper'
require 'performance_test_help'
class ListComprehensionTest < ActionController::PerformanceTest
TEST_ARRAY = (1..100).to_a
def test_map_compact
1000.times do
TEST_ARRAY.map{|x| x % 2 == 0 ? x * 3 : nil}.compact
end
end
def test_select_map
1000.times do
TEST_ARRAY.select{|x| x % 2 == 0 }.map{|x| x * 3}
end
end
def test_inject
1000.times do
TEST_ARRAY.inject([]) {|all, x| all << x*3 if x % 2 == 0; all }
end
end
end
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/performance/list_comprehension_test.rb" -- --benchmark
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
ListComprehensionTest#test_inject (1230 ms warmup)
wall_time: 1221 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.ListComprehensionTest#test_map_compact (860 ms warmup)
wall_time: 855 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.ListComprehensionTest#test_select_map (961 ms warmup)
wall_time: 955 ms
memory: 0.00 KB
objects: 0
gc_runs: 0
gc_time: 0 ms
.
Finished in 66.683039 seconds.
15 tests, 0 assertions, 0 failures, 0 errors
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.
An alternative solution that will work in every implementation and run in O(n) instead of O(2n) time is:
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
There seems to be some confusion amongst Ruby programmers in this thread concerning what list comprehension is. Every single response assumes some preexisting array to transform. But list comprehension's power lies in an array created on the fly with the following syntax:
squares = [x**2 for x in range(10)]
The following would be an analog in Ruby (the only adequate answer in this thread, AFAIC):
a = Array.new(4).map{rand(2**49..2**50)}
In the above case, I'm creating an array of random integers, but the block could contain anything. But this would be a Ruby list comprehension.
I've just published the comprehend gem to RubyGems, which lets you do this:
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
It's written in C; the array is only traversed once.
Enumerable has a grep
method whose first argument can be a predicate proc, and whose optional second argument is a mapping function; so the following works:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
This isn't as readable as a couple of other suggestions (I like anoiaque's simple select.map
or histocrat's comprehend gem), but its strengths are that it's already part of the standard library, and is single-pass and doesn't involve creating temporary intermediate arrays, and doesn't require an out-of-bounds value like nil
used in the compact
-using suggestions.
This is more concise:
[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
[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.
select(&:even?).map()
actually looks better, after seeing it below.
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.
Something like this:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
Call it:
lazy (1..6){|x| x * 3 if x.even?}
Which returns:
=> [6, 12, 18]
Another solution but perhaps not the best one
some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }
or
some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.push(x * 3) : nil }
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.