407

値をmap変更するか、値を nil に設定する があります。次に、リストから nil エントリを削除します。リストを保持する必要はありません。

これは私が現在持っているものです:

# A simple example function, which returns a value or nil
def transform(n)
  rand > 0.5 ? n * 10 : nil }
end

items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]

ループを実行して、次のように条件付きで別の配列に収集できることはわかっています。

new_items = []
items.each do |x|
    x = transform(x)
    new_items.append(x) unless x.nil?
end
items = new_items

しかし、それは慣用句ではないようです。関数をリストにマップして、nil を削除/除外する良い方法はありますか?

4

9 に答える 9

1049

使用できますcompact

[1, nil, 3, nil, nil].compact
=> [1, 3] 

mapブロックの出力として nil を含む配列を取得していて、そのブロックが条件付きで値を返そうとする場合は、コードの匂いがして、ロジックを再考する必要があることを人々に思い出させたいと思います。

たとえば、これを行う何かをしている場合:

[1,2,3].map{ |i|
  if i % 2 == 0
    i
  end
}
# => [nil, 2, nil]

それからしないでください。代わりに、の前にmapreject不要なものまたは必要selectなもの:

[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
  i
}
# => [2]

混乱を一掃するために使用compactするのは、正しく処理できなかったものを取り除くための最後の努力だと考えています。プログラムでどのような種類のデータがスローされているかを常に知っておく必要があります。予期しない/不明なデータは不良です。作業中の配列に nil が表示されるたびに、それらが存在する理由を掘り下げ、配列を生成するコードを改善できないかどうかを確認します。Ruby が nil を生成して時間とメモリを浪費し、配列をふるいにかけて削除するのではなく、配列を生成するコードを改善できるかどうかを確認します。それらは後で。

'Just my $%0.2f.' % [2.to_f/100]
于 2012-11-21T02:30:55.620 に答える
66

Ruby 2.7+

There is now!

Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.

For example:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

In your case, as the block evaluates to falsey, simply:

items.filter_map { |x| process_x url }

"Ruby 2.7 adds Enumerable#filter_map" is a good read on the subject, with some performance benchmarks against some of the earlier approaches to this problem:

N = 100_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
  x.report("select + map")  { N.times { enum.select { |i| i.even? }.map{ |i| i + 1 } } }
  x.report("map + compact") { N.times { enum.map { |i| i + 1 if i.even? }.compact } }
  x.report("filter_map")    { N.times { enum.filter_map { |i| i + 1 if i.even? } } }
end

# Rehearsal -------------------------------------------------
# select + map    8.569651   0.051319   8.620970 (  8.632449)
# map + compact   7.392666   0.133964   7.526630 (  7.538013)
# filter_map      6.923772   0.022314   6.946086 (  6.956135)
# --------------------------------------- total: 23.093686sec
# 
#                     user     system      total        real
# select + map    8.550637   0.033190   8.583827 (  8.597627)
# map + compact   7.263667   0.131180   7.394847 (  7.405570)
# filter_map      6.761388   0.018223   6.779611 (  6.790559)
于 2019-08-02T09:42:37.780 に答える
30

たとえば、空の文字列と nil を拒否するなど、より緩い拒否基準が必要な場合は、次のように使用できます。

[1, nil, 3, 0, ''].reject(&:blank?)
 => [1, 3, 0] 

さらに進んでゼロ値を拒否する (またはより複雑なロジックをプロセスに適用する) 場合は、拒否するブロックを渡すことができます。

[1, nil, 3, 0, ''].reject do |value| value.blank? || value==0 end
 => [1, 3]

[1, nil, 3, 0, '', 1000].reject do |value| value.blank? || value==0 || value>10 end
 => [1, 3]
于 2014-08-11T21:22:01.670 に答える
0

それを達成するためのもう1つの方法は、以下に示すとおりです。ここでは、 を使用Enumerable#each_with_objectして値を収集し、メソッドの結果をチェックするためにObject#tap必要な一時変数を取り除くためにを使用します。nilprocess_x

items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}

説明のための完全な例:

items = [1,2,3,4,5]
def process x
    rand(10) > 5 ? nil : x
end

items.each_with_object([]) {|x, obj| (process x).tap {|r| obj << r unless r.nil?}}

別のアプローチ:

呼び出しているメソッドを見ると、そのメソッドprocess_x urlの入力の目的が明確ではありませんx。の値をxいくつか渡して処理しurl、どのxs が実際に有効な非 nil 結果に処理されるかを判断すると仮定すると、Enumerabble.group_byは よりも優れたオプションである可能性がありますEnumerable#map

h = items.group_by {|x| (process x).nil? ? "Bad" : "Good"}
#=> {"Bad"=>[1, 2], "Good"=>[3, 4, 5]}

h["Good"]
#=> [3,4,5]
于 2015-12-30T21:50:46.803 に答える