Chris
Chris

Reputation:

How do you check whether a range contains a subset of another range?

If I have two ranges that overlap:

x = 1..10
y = 5..15

When I say:

puts x.include? y 

the output is:

false 

because the two ranges only overlap partially.

But if I want it to be "true" when there is partial overlap between two ranges, how would I write that? In other words I need a way to know when one range contains a subset of another range. I assume there's an elegant way to write this in Ruby but the only solutions I can think of are verbose.

Upvotes: 18

Views: 9309

Answers (10)

justapilgrim
justapilgrim

Reputation: 6872

If you're using Ruby 2.6, you can use Range#cover? with another Range.

(1..5).cover?(2..3)     #=> true
(1..5).cover?(0..6)     #=> false
(1..5).cover?(1...6)    #=> true

Upvotes: 6

Lewy
Lewy

Reputation: 719

Rails has Range#overlaps?

def overlaps?(other)
  cover?(other.first) || other.cover?(first)
end

Upvotes: 1

Khaja Minhajuddin
Khaja Minhajuddin

Reputation: 6741

This method can be used to test overlap between multiple ranges in an efficient way:

def range_overlap?(ranges)
  sorted_ranges = ranges.sort
  sorted_ranges.each_cons(2).each do |r1, r2|
    return true if r2.first <= r1.last
  end
  return false
end


def test(r)
  puts r.inspect, range_overlap?(r)
  puts '================'
  r = r.reverse
  puts r.inspect, range_overlap?(r)
  puts '================'
end


test [[1,9], [10, 33]]
test [[1,10], [5, 8]]
test [[1,10], [10, 33]]

Upvotes: 2

pguardiario
pguardiario

Reputation: 54984

Some helpful enumerable methods:

# x is a 'subset' of y
x.all?{|n| y.include? n}
# x and y overlap
x.any?{|n| y.include? n}
# x and y do not overlap
x.none?{|n| y.include? n}
# x and y overlap one time
x.one?{|n| y.include? n}

Upvotes: -1

Abe Voelker
Abe Voelker

Reputation: 31574

You could also convert the ranges to sets, since you're basically doing set intersection here. Might be easier if you are dealing with more than two ranges.

x = (1..10).to_set
y = (5..15).to_set
!(x & y).empty? #returns true (true == overlap, false == no overlap)

Upvotes: 2

ski
ski

Reputation: 2471

Be careful using this with large ranges but this is an elegant way to do it:

(x.to_a & y.to_a).empty?

Upvotes: 7

Xiong Chiamiov
Xiong Chiamiov

Reputation: 13714

If you're checking for overlap, then I'd just do

(x.include? y.first) or (x.include? y.last)

as one range will have to include at least one of the ends of the other. This is more intuitive to me than the accepted conjuction answer, though not quite as efficient as MarkusQ's limit comparison.

Upvotes: 1

Soleone
Soleone

Reputation: 189

But if I want it to be "true" when there is partial overlap between two ranges, how would I write that?

You can convert the ranges to an array, and use the & operator (conjunction). This returns a new array with all the elements occuring in both arrays. If the resulting array is not empty, that means, that there are some overlapping elements:

def overlap?(range_1, range_2)
  !(range_1.to_a & range_2.to_a).empty?
end

Upvotes: 1

Evgeny Zislis
Evgeny Zislis

Reputation: 6957

If a range includes either the beginning or the end of a second range, then they overlap.

(x === y.first) or (x === y.last)

is the same as this:

x.include?(y.first) or x.include?(y.last)

Upvotes: 1

MarkusQ
MarkusQ

Reputation: 21950

The efficient way is to compare the limits

(x.first <= y.last) and (y.first <= x.last)

Upvotes: 64

Related Questions