ironsand ironsand - 3 months ago 10
Ruby Question

Why Range#include? is much slower than greater or less than operator

I have a array of hashes which key are

and value are
This is a test code to emulate it.

hashes = do |i|
[ - i.days, rand(100)]

I want to get values of a specific period.
At first I wrote with
, but it was quite slow.

Benchmark.measure do{|k,v| (,3,3),6,10)).include?(k)}

#<Benchmark::Tms:0x007fd16479bed0 @label="", @real=2.9242447479628026, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=2.920000000000016, @total=2.920000000000016>

With simple greater or less than operator it became 60 times faster.

Benchmark.measure do{|k,v| k >=,3,3) && k <=,6,10)}

#<Benchmark::Tms:0x007fd162b61670 @label="", @real=0.05436371313408017, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.05000000000001137, @total=0.05000000000001137>

I thought these two expression are basically same.

Why there is so big difference?


You need to use Range#cover? instead of Range#include?, and to calculate the range just once, not once for each element of measure. cover? compares the block variable k with the end-points of the range; include? (for non-numeric objects, such as dates) compares each element in the range with the block variable until it finds a match or concludes there is no match (similar to Array#include?).

In addition, you wish to consider the first and only key of each element of hashes (a hash), so if that hash is h, the first key-value pair is h.first, and the key of that pair is h.first.first.

require 'date'

Benchmark.measure do
  r =,3,3),6,10){|h| r.cover? h.first.first }

This should be nearly identical to your second method in terms of execution speed.

An example

hashes = [{,3,1)=>1 }, {,4,20)=>2 },
          {,6,10)=>3 }, {,6,11)=>4 }] 
  #=> [{#<Date: 2012-03-01 ((2455988j,0s,0n),+0s,2299161j)>=>1},
  #    {#<Date: 2012-04-20 ((2456038j,0s,0n),+0s,2299161j)>=>2},
  #    {#<Date: 2012-06-10 ((2456089j,0s,0n),+0s,2299161j)>=>3},
  #    {#<Date: 2012-06-11 ((2456090j,0s,0n),+0s,2299161j)>=>4}] 

r =,3,3),6,10){|h| r.cover? h.first.first }
  #=> {#<Date: 2012-04-20 ((2456038j,0s,0n),+0s,2299161j)>=>2,
  #    #<Date: 2012-06-10 ((2456089j,0s,0n),+0s,2299161j)>=>3}