Rubyの内包表記を一覧表示する


93

Pythonリスト内包表記に相当することを行うには、次のようにします。

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

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


3
あなたとグレン・マクドナルドの両方の答えは私には問題ないようです...どちらかより簡潔にしようとすることで何が得られるかわかりません。
ピストス

1
このソリューションは、リストを2回横断します。インジェクトはしません。
Pedro Rolo

2
ここでいくつかの素晴らしい答えがありますが、複数のコレクションにわたるリストの理解のためのアイデアも素晴らしいでしょう。
Bo

回答:


55

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

私はおそらくあなたがしたのと同じ方法でそれを行うでしょう。


2
コンパクトに使えました!少し最適化するには
Alexey

9
これは実際には正しくありません。検討すると、は [nil, nil, nil].comprehend {|x| x }を返します[]
2013年

ドキュメントによると、alexeyは、compact!変更されたアイテムがない場合、配列ではなくnilを返すため、機能しないと思います。
Binary Phile 2014

89

どのように「試合:

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

少なくとも私の好みでは、少しクリーンで、クイックベンチマークテストによると、バージョンよりも約15%高速です...


4
だけsome_array.map{|x| x * 3 unless x % 2}.compactでなく、間違いなく読みやすく/ルビー風です。
ナイトプール

5
unless x%2ルビでは0が真であるため、@ nightpool は効果がありません。参照:gist.github.com/jfarmer/2647362
Abhinav Srivastava

30

3つの代替案を比較する簡単なベンチマークを作成しましたが、map-compactは本当に最良のオプションのようです。

パフォーマンステスト(Rails)

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

1
reduceこのベンチマークでも見ると面白いでしょう(stackoverflow.com/a/17703276を参照)。
Adam Lindberg、

3
inject==reduce
ben.snape 2015

map_compactはおそらく高速ですが、新しい配列を作成しています。注入は、map.compactやselect.mapよりもスペース効率が良い
bibstha

11

このスレッドでは、リスト内包とは何かについて、Rubyプログラマの間で混乱が生じているようです。すべての単一の応答は、変換するいくつかの既存の配列を想定しています。しかし、リスト内包の力は、次の構文でその場で作成された配列にあります。

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

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

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

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


1
OPが実行しようとしていることをどのように実行しますか?
アンドリューグリム

2
実際、OP自体に、作成者が変換したい既存のリストがあることがわかりました。しかし、リスト内包の典型的な概念には、反復を参照することにより、以前は存在しなかった配列/リストを作成することが含まれます。しかし、実際には、いくつかの正式な定義では、リスト内包表記はマップをまったく使用できないと記載されているため、私のバージョンでさえコーシャではありません。
マーク

5
Rubyの例がPythonの例のアナログであるはずの方法がわかりません。Rubyコードは次のようになります。squares=(0..9).map {| x | x ** 2}
michau

4
@michauは正しいですが、リスト内包表記(Markが無視した)の全体的なポイントは、リスト内包表記自体が配列を生成しないのではなく、ジェネレーターとcoルーチンを使用して、ストレージをまったく割り当てずにすべての計算をストリーミング方式で実行することです(例外)一時変数)(iff)結果が配列変数に配置されるまで-これは、Pythonの例で角括弧の目的であり、理解を一連の結果にまとめます。Rubyには、ジェネレーターに似た機能はありません。
ガス2014

4
ああ、そうです(Ruby 2.0以降):squares_of_all_natural_numbers =(0..Float :: INFINITY).lazy.map {| x | x ** 2}; p squares_of_all_natural_numbers.take(10).to_a
michau

11

私はこのトピックについて、Rein Henrichsと話しました。

map { ... }.compact

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

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

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

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


9

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

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

11
つまり、リストを1回だけトラバースします。正式な定義で言えば、O(n)はO(2n)と等しくなります。ちょうどつまらない:)
ダニエルヘッパー

1
@Daniel Harper :)あなたは正しいだけでなく、平均的なケースでも、リストを一度横断していくつかのエントリを破棄し、次にもう一度操作を実行すると、平均的なケースで実際に優れています:)
Pedro Rolo

言い換えれば、あなたがやっている2事をn代わりに倍1もののn倍とし、別1のもののn時間:) 1つの重要な利点のはinject/ reduceそれがすべての保存していることであるnilより多くのリスト、comprehensionly行動である入力シーケンス内の値
ジョン・ラRooy

8

RubyGemsに包括的なgemを公開しました。これにより、次のことが可能になります。

require 'comprehend'

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

それはCで書かれています。配列は一度だけトラバースされます。


7

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

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

これは他のいくつかの提案(私はanoiaqueの単純なselect.mapまたは歴史家の包括的なgemが好きです)ほど読みやすくはありませんが、その長所はすでに標準ライブラリの一部であり、一時的な中間配列の作成を含まないことです。 、および-using提案でnil使用されているような範囲外の値は必要ありませんcompact



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

それは私にとってはうまくいきます。綺麗です。はい、それはと同じですが、コードをより理解しやすくするmapと思いcollectます。


select(&:even?).map()

下を見てみると、実際はもっとよく見えます。


2

ペドロが述べたように、あなたはにチェーンのコールを一緒に融合することができますEnumerable#selectし、Enumerable#map選択した要素の上に横断を避け、。Enumerable#selectはfoldまたはの特殊化であるため、これは当てはまりますinject急いで紹介を投稿しましたRubyのサブレディットでこのトピックの。

手動で配列変換を融合するのは面倒な場合があるため、誰かがRobert Gambleのcomprehend実装をいじって、このselect/ mapパターンをより美しくすることができます。


2

このようなもの:

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

あれを呼べ:

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

どちらが戻ります:

=> [6, 12, 18]

lazy配列での定義の何が問題になっているのか(1..6).lazy{|x|x*3 if x.even?}
Guss

1

別の解決策ですが、おそらく最良の解決策ではありません

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 }

0

これはこれに取り組む1つの方法です。

c = -> x do $*.clear             
  if x['if'] && x[0] != 'f' .  
    y = x[0...x.index('for')]    
    x = x[x.index('for')..-1]
    (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
    x.insert(x.length, "end; $*")
    eval(x)
    $*)
  elsif x['if'] && x[0] == 'f'
    (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << x")
    x.insert(x.length, "end; $*")
    eval(x)
    $*)
  elsif !x['if'] && x[0] != 'f'
    y = x[0...x.index('for')]
    x = x[x.index('for')..-1]
    (x.insert(x.index(x.split[3]) + x.split[3].length, " do $* << #{y}")
    x.insert(x.length, "end; $*")
    eval(x)
    $*)
  else
    eval(x.split[3]).to_a
  end
end 

したがって、基本的には、文字列をループの適切なルビ構文に変換してから、文字列でpython構文を使用して実行できます。

c['for x in 1..10']
c['for x in 1..10 if x.even?']
c['x**2 for x in 1..10 if x.even?']
c['x**2 for x in 1..10']

# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# [2, 4, 6, 8, 10]
# [4, 16, 36, 64, 100]
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

または、文字列の見え方が気に入らない場合や、ラムダを使用する必要がない場合は、Python構文をミラー化する試みを行わずに、次のようなことを行うことができます。

S = [for x in 0...9 do $* << x*2 if x.even? end, $*][1]
# [0, 4, 8, 12, 16]



-4

私が最も理解しているものは次のようになると思います:

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

Rubyでは式の後に条件を置くことができるため、リスト内包表記のPythonバージョンと同様の構文が得られます。また、このselectメソッドにはに相当するものが何も含まれていないためfalse、結果のリストからすべてのnil値が削除され、mapまたはを使用した場合のように、compactを呼び出す必要はありませんcollect


7
これは機能していないようです。少なくともRuby 1.8.6では、[1,2,3,4,5,6] .select {| x | x * 3 if x%2 == 0}が[2、4、6]に評価される場合Enumerable#selectは、ブロックがtrueまたはfalseに評価されるかどうかのみを考慮し、出力する値はAFAIKではありません。
グレッグキャンベル、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.