Skip to content
  • Ricardo Díaz's avatar
    6af23552
    Use native Range#cover? which accepts Range arguments since Ruby 2.6 · 6af23552
    Ricardo Díaz authored
    Commit: https://github.com/ruby/ruby/commit/9ca738927293df1c7a2a1ed7e2d6cf89527b5438
    Discussion: https://bugs.ruby-lang.org/issues/14473
    
    It seems to be compatible with the original ActiveSupport's
    implementation, at least based on the test suite.
    
    It also works faster:
    
    ```
    Warming up --------------------------------------
     Ruby's Range#cover?     1.196M i/100ms
    ActiveSupport's Range#cover?
                           396.369k i/100ms
    Calculating -------------------------------------
     Ruby's Range#cover?     11.889M (± 1.7%) i/s -     59.820M in   5.033066s
    ActiveSupport's Range#cover?
                              3.951M (± 1.2%) i/s -     19.818M in   5.017176s
    
    Comparison:
     Ruby's Range#cover?: 11888979.3 i/s
    ActiveSupport's Range#cover?:  3950671.0 i/s - 3.01x  (± 0.00) slower
    ```
    
    Benchmark script:
    
    ```ruby
    require "minitest/autorun"
    require "benchmark/ips"
    
    module ActiveSupportRange
      def active_support_cover?(value)
        if value.is_a?(::Range)
          is_backwards_op = value.exclude_end? ? :>= : :>
          return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
          # 1...10 covers 1..9 but it does not cover 1..10.
          # 1..10 covers 1...11 but it does not cover 1...12.
          operator = exclude_end? && !value.exclude_end? ? :< : :<=
          value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
          cover?(value.first) && (self.end.nil? || value_max.public_send(operator, last))
        else
          cover?
        end
      end
    end
    
    class BugTest < Minitest::Test
      def test_range_cover
        Range.prepend(ActiveSupportRange)
    
        range = (1..10000)
    
        Benchmark.ips do |x|
          x.report("Ruby's Range#cover?") do
            range.cover?((100..20))
          end
    
          x.report("ActiveSupport's Range#cover?") do
            range.active_support_cover?((100..20))
          end
    
          x.compare!
        end
      end
    end
    ```
    6af23552
    Use native Range#cover? which accepts Range arguments since Ruby 2.6
    Ricardo Díaz authored
    Commit: https://github.com/ruby/ruby/commit/9ca738927293df1c7a2a1ed7e2d6cf89527b5438
    Discussion: https://bugs.ruby-lang.org/issues/14473
    
    It seems to be compatible with the original ActiveSupport's
    implementation, at least based on the test suite.
    
    It also works faster:
    
    ```
    Warming up --------------------------------------
     Ruby's Range#cover?     1.196M i/100ms
    ActiveSupport's Range#cover?
                           396.369k i/100ms
    Calculating -------------------------------------
     Ruby's Range#cover?     11.889M (± 1.7%) i/s -     59.820M in   5.033066s
    ActiveSupport's Range#cover?
                              3.951M (± 1.2%) i/s -     19.818M in   5.017176s
    
    Comparison:
     Ruby's Range#cover?: 11888979.3 i/s
    ActiveSupport's Range#cover?:  3950671.0 i/s - 3.01x  (± 0.00) slower
    ```
    
    Benchmark script:
    
    ```ruby
    require "minitest/autorun"
    require "benchmark/ips"
    
    module ActiveSupportRange
      def active_support_cover?(value)
        if value.is_a?(::Range)
          is_backwards_op = value.exclude_end? ? :>= : :>
          return false if value.begin && value.end && value.begin.public_send(is_backwards_op, value.end)
          # 1...10 covers 1..9 but it does not cover 1..10.
          # 1..10 covers 1...11 but it does not cover 1...12.
          operator = exclude_end? && !value.exclude_end? ? :< : :<=
          value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
          cover?(value.first) && (self.end.nil? || value_max.public_send(operator, last))
        else
          cover?
        end
      end
    end
    
    class BugTest < Minitest::Test
      def test_range_cover
        Range.prepend(ActiveSupportRange)
    
        range = (1..10000)
    
        Benchmark.ips do |x|
          x.report("Ruby's Range#cover?") do
            range.cover?((100..20))
          end
    
          x.report("ActiveSupport's Range#cover?") do
            range.active_support_cover?((100..20))
          end
    
          x.compare!
        end
      end
    end
    ```
Loading