Pythonリスト内包表記と同等のことを行うには、次のことを行います。
some_array.select{|x| x % 2 == 0 }.collect{|x| x * 3}
これを行うより良い方法はありますか?おそらく1つのメソッド呼び出しで?
本当にしたい場合は、次のようなArray#comprehendメソッドを作成できます。
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
プリント:
6
12
18
私はおそらくあなたがしたようにそれをするでしょう。
試合方法:
some_array.map {|x| x % 2 == 0 ? x * 3 : nil}.compact
少なくとも私の好みに合わせてわずかにクリーンで、お使いのバージョンよりも約15%高速なベンチマークテストによると...
3つの選択肢を比較する簡単なベンチマークを作成し、map-compactが本当に最適なオプションのようです。
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
このトピックについてRein Henrichsと話しました。ReinHenrichsは、最高のパフォーマンスのソリューションは
map { ... }.compact`
これは、Enumerable#inject
の不変の使用法のように中間配列を構築することを避け、割り当ての原因となる配列の成長を避けるため、理にかなっています。コレクションにnil要素を含めることができる場合を除き、他のすべてと同じくらい一般的です。
私はこれと比較していません
select {...}.map{...}
RubyのEnumerable#select
のC実装も非常に優れている可能性があります。
このスレッドでは、リストの理解とは何かについて、Rubyプログラマーの間で混乱が生じているようです。すべての応答は、変換する既存の配列を前提としています。しかし、リストの理解力は、次の構文:
squares = [x**2 for x in range(10)]
以下は、Ruby(このスレッドで唯一の適切な答え、AFAIC))の類似物です。
a = Array.new(4).map{Rand(2**49..2**50)}
上記の場合、ランダムな整数の配列を作成していますが、ブロックには何でも含めることができます。しかし、これはRubyリスト内包表記です。
すべての実装で機能し、O(n)時間の代わりにO(2n)で実行する)代替ソリューションは次のとおりです。
some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
comprehend gem をRubyGemsに公開したところ、これを行うことができます。
require 'comprehend'
some_array.comprehend{ |x| x * 3 if x % 2 == 0 }
Cで書かれています。配列は1回だけ走査されます。
Enumerableにはgrep
メソッドがあり、その最初の引数は述語procであり、オプションの2番目の引数はマッピング関数です。次のように動作します:
some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}
これは、他のいくつかの提案ほど読みやすいものではありません(anoiaqueのシンプルなselect.map
またはhistocratの包括的gem)が、その長所はすでに標準ライブラリの一部であり、シングルパスであり、一時的な中間配列の作成を伴わず、nil
のような範囲外の値を必要としないことです。 compact
- usingの提案で使用されます。
これはより簡潔です:
[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]
それは私のために働く。それもきれいです。はい、map
と同じですが、collect
はコードをより理解しやすくすると思います。
select(&:even?).map()
下で見た後、実際に良く見えます。
前述のPedroのように、Enumerable#select
およびEnumerable#map
へのチェーンされた呼び出しを融合して、選択した要素のトラバーサルを回避できます。 Enumerable#select
はfoldまたはinject
の特殊化であるため、これは事実です。 Ruby subreddit。)のトピックに、 ハスティ紹介 を投稿しました。
配列変換を手動で融合するのは面倒なので、誰かがRobert Gambleのcomprehend
実装を試して、このselect
/map
パターンをきれいにすることができます。
このようなもの:
def lazy(collection, &blk)
collection.map{|x| blk.call(x)}.compact
end
あれを呼べ:
lazy (1..6){|x| x * 3 if x.even?}
返されるもの:
=> [6, 12, 18]
別のソリューションですが、おそらく最良のソリューションではありません
some_array.flat_map {|x| x % 2 == 0 ? [x * 3] : [] }
または
some_array.each_with_object([]) {|x, list| x % 2 == 0 ? list.Push(x * 3) : nil }