web-dev-qa-db-ja.com

Rubyのリスト内包表記

Pythonリスト内包表記と同等のことを行うには、次のことを行います。

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

これを行うより良い方法はありますか?おそらく1つのメソッド呼び出しで?

85
Readonly

本当にしたい場合は、次のような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

私はおそらくあなたがしたようにそれをするでしょう。

53
Robert Gamble

試合方法:

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

少なくとも私の好みに合わせてわずかにクリーンで、お使いのバージョンよりも約15%高速なベンチマークテストによると...

84
glenn mcdonald

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
28
knuton

このトピックについてRein Henrichsと話しました。ReinHenrichsは、最高のパフォーマンスのソリューションは

map { ... }.compact`

これは、Enumerable#injectの不変の使用法のように中間配列を構築することを避け、割り当ての原因となる配列の成長を避けるため、理にかなっています。コレクションにnil要素を含めることができる場合を除き、他のすべてと同じくらい一般的です。

私はこれと比較していません

select {...}.map{...}

RubyのEnumerable#selectのC実装も非常に優れている可能性があります。

11
jvoorhis

このスレッドでは、リストの理解とは何かについて、Rubyプログラマーの間で混乱が生じているようです。すべての応答は、変換する既存の配列を前提としています。しかし、リストの理解力は、次の構文:

squares = [x**2 for x in range(10)]

以下は、Ruby(このスレッドで唯一の適切な答え、AFAIC))の類似物です。

a = Array.new(4).map{Rand(2**49..2**50)} 

上記の場合、ランダムな整数の配列を作成していますが、ブロックには何でも含めることができます。しかし、これはRubyリスト内包表記です。

10
Mark

すべての実装で機能し、O(n)時間の代わりにO(2n)で実行する)代替ソリューションは次のとおりです。

some_array.inject([]){|res,x| x % 2 == 0 ? res << 3*x : res}
9

comprehend gem をRubyGemsに公開したところ、これを行うことができます。

require 'comprehend'

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

Cで書かれています。配列は1回だけ走査されます。

8
histocrat

Enumerableにはgrepメソッドがあり、その最初の引数は述語procであり、オプションの2番目の引数はマッピング関数です。次のように動作します:

some_array.grep(proc {|x| x % 2 == 0}) {|x| x*3}

これは、他のいくつかの提案ほど読みやすいものではありません(anoiaqueのシンプルなselect.mapまたはhistocratの包括的gem)が、その長所はすでに標準ライブラリの一部であり、シングルパスであり、一時的な中間配列の作成を伴わず、nilのような範囲外の値を必要としないことです。 compact- usingの提案で使用されます。

7
Peter Moulder

これはより簡潔です:

[1,2,3,4,5,6].select(&:even?).map{|x| x*3}
4
anoiaque
[1, 2, 3, 4, 5, 6].collect{|x| x * 3 if x % 2 == 0}.compact
=> [6, 12, 18]

それは私のために働く。それもきれいです。はい、mapと同じですが、collectはコードをより理解しやすくすると思います。


select(&:even?).map()

下で見た後、実際に良く見えます。

4
eyko

前述のPedroのように、Enumerable#selectおよびEnumerable#mapへのチェーンされた呼び出しを融合して、選択した要素のトラバーサルを回避できます。 Enumerable#selectはfoldまたはinjectの特殊化であるため、これは事実です。 Ruby subreddit。)のトピックに、 ハスティ紹介 を投稿しました。

配列変換を手動で融合するのは面倒なので、誰かがRobert Gambleのcomprehend実装を試して、このselect/mapパターンをきれいにすることができます。

2
jvoorhis

このようなもの:

def lazy(collection, &blk)
   collection.map{|x| blk.call(x)}.compact
end

あれを呼べ:

lazy (1..6){|x| x * 3 if x.even?}

返されるもの:

=> [6, 12, 18]
2
Alexandre Magro

別のソリューションですが、おそらく最良のソリューションではありません

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 }
1
joegiralt