Better Range Intersection in Ruby

   By chris on June 6th 2009 in /dev/random | 1,169 views

A little while ago I posted a solution for finding intersections between timespans (“Detecting DateTime Timespan Overlap In Ruby”). It works but, as Dan Kubb pointed out to me, it doesn’t use the most graceful means of determining intersections between Range instances, particularly for very large ranges.

Not content to leave it at that, Dan of course provides a better solution, complete with tests:

#!/usr/bin/env ruby

class Range
  def intersection(other)
    raise ArgumentError, 'value must be a Range' unless other.kind_of?(Range)

    min, max = first, exclude_end? ? max : last
    other_min, other_max = other.first, other.exclude_end? ? other.max : other.last

    new_min = self === other_min ? other_min : other === min ? min : nil
    new_max = self === other_max ? other_max : other === max ? max : nil

    new_min && new_max ? new_min..new_max : nil
  end

  alias_method :&, :intersection
end

if __FILE__ == $0
  range = 5..10

  tests = {
    1..4   => nil,     # before
    11..15 => nil,     # after
    1..6   => 5..6,    # overlap_begin
    9..15  => 9..10,   # overlap_end
    1..5   => 5..5,    # overlap_begin_edge
    10..15 => 10..10,  # overlap_end_edge
    5..10  => 5..10,   # overlap_all
    6..9   => 6..9,    # overlap_inner

    1...5  => nil,     # before       (exclusive range)
    1...7  => 5..6,    # overlap_begin      (exclusive range)
    1...6  => 5..5,    # overlap_begin_edge (exclusive range)
    5...11 => 5..10,   # overlap_all  (exclusive range)
    6...10 => 6..9,    # overlap_inner      (exclusive range)
  }

  tests.each do |other, expected|
    result = range.intersection(other)
    result_status = ( result == expected ) ? "passed" : "failed"
    puts "#{range.inspect} #{other.inspect} result #{result_status}: #{result.inspect} (#{expected.inspect})"
  end
end

I like it.

Comments are closed.

Trackback URI |