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.
