Reputation: 4748
I know that I can do:
(1..30).cover?(2)
=> true
But when I try to do the same with another range it always returns false:
(1..30).cover?(2..3)
=> false
So my question is - is there any elegant way to compare two ranges in ruby? In my case I want to check if two DateTime-ranges overlap. Thanks in advance.
Upvotes: 9
Views: 6490
Reputation: 106802
Two ranges overlap for a given range A when:
Examples:
Range A |-----|
|-----| Case 1
|-----| Case 2
|-| Case 1 + 2
|---------| Case 3
Looking closer, the rule is: Two ranges overlap when Range B starts before the range A ends and range B ends after the range A starts.
def ranges_overlap?(range_a, range_b)
range_b.begin <= range_a.end && range_a.begin <= range_b.end
end
Or, when you are using the latest version of Ruby, then you can use the Range#overlap?
method that was introduced in Ruby 3.3, like this:
range_a.overlap?(range_b)
Upvotes: 16
Reputation: 15248
There is Range#overlap?
In Ruby 3.3+
It returns true
if ranges overlap each other, otherwise false
It works with different kinds of ranges:
(5..10).overlap?(10..20)
# => true
('a'..'e').overlap?('b'..'f')
# => true
It returns false
when begin > end:
(5..1).overlap?(2..3)
# => false
It returns false
when try to check incomparable kinds of ranges:
(5..10).overlap?('a'..'e')
# => false
It works with exclusive ranges:
(5..10).overlap?(1...5)
# => false
(1...5).overlap?(5..10)
# => false
It works with endless and beginless ranges:
(1..).overlap?(..1)
# => true
(1..).overlap?(...1)
# => false
It raises error when try to pass not range
(1..5).overlap?(1)
# raise TypeError (expected Range)
Upvotes: 3
Reputation: 14965
If you want the rails implementation you can use
class Range
def overlaps?(other)
cover?(other.first) || other.cover?(first)
end
end
(1..5).overlaps?(4..6) # => true
(1..5).overlaps?(7..9) # => false
Upvotes: 6
Reputation: 479
In rails You can use (1..3).overlaps?(2..4) # true
https://apidock.com/rails/Range/overlaps
Upvotes: 4
Reputation: 110675
def overlap?(r1,r2)
!(r1.first > r2.last || r1.last < r2.first)
end
overlap? 1..5, 4..10 #=> true
overlap? 1..5, 6..10 #=> false
overlap? 1..10, 4..8 #=> true
overlap? 1..4, 4..8 #=> true
The operative line is equivalent to:
r1.first <= r2.last && r1.last >= r2.first
I normally try to avoid negation, but in this case I think it reads better with it.
Another way:
def overlap?(r1,r2)
!(([r1.first, r2.first].min..[r1.last, r2.last].max).size >= r1.size + r2.size)
end
overlap? 1..5, 4..10 #=> true
overlap? 1..5, 6..10 #=> false
overlap? 1..10, 4..8 #=> true
overlap? 1..4, 4..8 #=> true
The operative line is equivalent to:
([r1.first, r2.first].min..[r1.last, r2.last].max).size < r1.size + r2.size
Again, I prefer the one with negation.
Upvotes: 8
Reputation: 2351
You can check Overlapping using
range1.first < range2.last && range2.first < range1.last
You can add it as an instance method of range or as a helper method somewhere in you data.
source: https://stackoverflow.com/a/325964/4091324
Upvotes: 0
Reputation: 66837
While the conversions may be wasteful, semantically comparing sets seems to make the most sense:
Set.new(1..30).superset?(Set.new(2..3))
#=> true
Set.new(1..30).superset?(Set.new(0..3))
#=> false
If you don't want to do that, you can do something like this (with r1
and r2
being ranges):
r1.cover?(r2.min) && r1.cover?(r2.max)
Upvotes: 2