次のように Date な Range では #include? よりも #cover? の方が高速で動作します。
require 'benchmark' require "date" range = (Date.parse("2020/01/01")..Date.parse("2021/01/01")) date = Date.parse("2020/05/01") Benchmark.bm(20) do |b| b.report('Date include?') { 10_000.times { range.include?(date) } } b.report('Date cover?') { 10_000.times { range.cover?(date) } } end __END__ user system total real Date include? 0.720505 0.000000 0.720505 ( 0.720700) Date cover? 0.001932 0.000000 0.001932 ( 0.001932)
なぜパフォーマンスに差が出るのか
これは Range#include? が Range を『離散値』として扱うのに対して Range#cover? は『連続値』として扱うためです。
内部的な挙動でいうと Range#include? は Enumerable#include? を呼び出しており線形的に値を探査します。
一方で Range#cover? は始端と終端を <=> で比較しているだけなのでより高速に動作します。
なので Range が『連続値』であることが保証されているのであれば Range#cover? を使ったほうがより高速に動作します。
require 'benchmark' require "date" range = (Date.parse("2020/01/01")..Date.parse("2021/01/01")) date = Date.parse("2020/05/01") Benchmark.bm(20) do |b| b.report('Date include?') { 10_000.times { range.include?(date) } } # Range#include? は実質 Enumerable#include? と同じ b.report('Date each.include?') { 10_000.times { range.each.include?(date) } } end __END__ user system total real Date include? 0.730970 0.000163 0.731133 ( 0.731358) Date each.include? 0.743971 0.000008 0.743979 ( 0.744230)
ちなみに Range#include? と Range#cover? で挙動が違うケースもあるので注意しましょう。
p ("a" .. "c").include?("ba") # => false p ("a" .. "c").cover?("ba") # => true # これは # "a" <=> "ba" # => -1 # "c" <=> "ba" # => 1 # となるため "a" .. "c" の範囲に含まれてしまう
Range が数値だった場合は?
ちなみに Range の要素が数値の場合は #include? は #cover? と同等の動きがするのでパフォーマンス的な懸念点はありません。
require 'benchmark' require "date" range = (Date.parse("2020/01/01")..Date.parse("2021/01/01")) date = Date.parse("2020/05/01") Benchmark.bm(20) do |b| b.report('Integer include?') { 10_000.times { (1..1000).include?(500) } } b.report('Integer cover?') { 10_000.times { (1..1000).cover?(500) } } end __END__ user system total real Integer include? 0.001725 0.000000 0.001725 ( 0.001724) Integer cover? 0.001854 0.000000 0.001854 ( 0.001855)