やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

【Ruby】配列が特定の要素を含むかを判定するならany?ではなくinclude?を使うべし

ある日のこと。Rubyのコードを読んでいると配列の要素が特定の値を含んでいるかどうかの判定にinclude?を使っている箇所とany?を使っている箇所があることに気づきました。

lst = [1,2,3,4,5]

puts lst.any?(1) # true
puts lst.include?(1) # true

たしかに、どちらを使っても得られる結果は同じです。しかし「配列が特定の値を含んでいるか」という命題に対して、直感的なインターフェースなのはinclude?でしょう。わざわざany?を使う必要性は感じません。

include?とany?の違い

include?は引数に値を指定することしかできませんが、any?はブロックを指定することができます。

puts [1,2,3,4,5].any? { |n| n == 4 } # true

またany?Arrayクラスに実装されているメソッドではなく、Enumerableに実装されているためEnumerableを継承している

  • Array
  • Hash
  • Range
  • Set

などで使用することができます。

require 'set'

array_ =  [1,2,3,4,5].any? { |n| n == 4 } # true
hash_ = { a: 1, b: 2, c: 3 }.any? { |_k, v| v == 2 } # true
range_ = (1..10).any? { |n| n == 5 } # true
set_ = Set[1,2,3,4,5].any? { |n| n == 3 } # true

ただし、ブロック引数を取れる分、内部での判定が複雑になるためinclude?と比べると、どのケースでも実行速度が劣ります。

bench_mark.rb

require 'benchmark'

big_array = (1..10000).to_a
Benchmark.bm do |x|
  x.report('[any?] 最小値の探索') { big_array.any? { |n| n == 1 } }
  x.report('[include?] 最小値の探索') { big_array.include?(1) }

  x.report('[any?] 中央値の探索') { big_array.any? { |n| n == 5000 } }
  x.report('[include?] 中央値の探索') { big_array.include?(5000) }

  x.report('[any?] 最大値の探索') { big_array.any? { |n| n == 10000 } }
  x.report('[include?] 最大値の探索') { big_array.include?(10000) }
end

結果

> $ ruby array_bench.rb 
       user     system      total        real
[any?] 最小値の探索  0.000004   0.000004   0.000008 (  0.000001)
[include?] 最小値の探索  0.000001   0.000001   0.000002 (  0.000001)
[any?] 中央値の探索  0.000141   0.000000   0.000141 (  0.000141)
[include?] 中央値の探索  0.000019   0.000000   0.000019 (  0.000019)
[any?] 最大値の探索  0.000275   0.000000   0.000275 (  0.000276)
[include?] 最大値の探索  0.000038   0.000000   0.000038 (  0.000038)

結論

「配列が特定の値を含んでいるか」という場合なら脳死でinclude?で良いかと思います。
include?が使えない配列以外の値、配列が含むデータの構造が複雑、条件式が==ではない場合などではany?を使った方が良いでしょう。

any?を使った方が良いケース

users = [
  { id: 1, name: 'satou', age: 30 },
  { id: 2, name: 'tanaka', age: 45 },
  { id: 3, name: 'takahashi', age: 21 },
  { id: 4, name: 'suzuki', age: 28 },
]

over_40_user_exist = users.any? { |user| user[:age] >= 40 }
puts over_40_user_exist # true

参考文献