Ruby: intersection between two ranges

好久不见. 提交于 2019-11-28 10:05:13
require 'date'

class Range
  def intersection(other)
    return nil if (self.max < other.begin or other.max < self.begin) 
    [self.begin, other.begin].max..[self.max, other.max].min
  end
  alias_method :&, :intersection
end

p (Date.new(2011,1,1)..Date.new(2011,1,15)) & (Date.new(2011,1,10)..Date.new(2011,2,15))
#<Date: 2011-01-10 ((2455572j,0s,0n),+0s,2299161j)>..#<Date: 2011-01-15 ((2455577j,0s,0n),+0s,2299161j)>

You can try this to get a range representing intersection

range1 = Date.new(2011,12,1)..Date.new(2011,12,10)
range2 = Date.new(2011,12,4)..Date.new(2011,12,12)

inters = range1.to_a & range2.to_a

intersected_range = inters.min..inters.max

Converting your example:

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

    inters = self.to_a & other.to_a

    inters.empty? ? nil : inters.min..inters.max 
  end  

  alias_method :&, :intersection  
end

I found this: http://www.postal-code.com/binarycode/2009/06/06/better-range-intersection-in-ruby/ which is a pretty good start, but does not work for dates. I've tweaked a bit into this:

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

    new_min = self.cover?(other.min) ? other.min : other.cover?(min) ? min : nil  
    new_max = self.cover?(other.max) ? other.max : other.cover?(max) ? max : nil  

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

  alias_method :&, :intersection  
end

I've omitted the tests, but they are basically the tests from the post above changed for dates. This works for ruby 1.9.2.

Anyone got a better solution?

bert bruynooghe

I baked this solution for ascending ranges, also taking care of the exclude end situations:

intersect_ranges = ->(r1, r2) do
  new_end = [r1.end, r2.end].min
  new_begin = [r1.begin, r2.begin].max
  exclude_end = (r2.exclude_end? && new_end == r2.end) || (r1.exclude_end? && new_end == r1.end)

  valid = (new_begin <= new_end && !exclude_end) 
  valid ||= (new_begin < new_end && exclude_end))
  valid ? Range.new(new_begin, new_end, exclude_end) : nil
end

I'm also a bit worried by you guys adding it to the Range class itself, since the behavior of intersecting ranges is not uniformly defined. (How about intersecting 1...4 and 4...1? Why nil when there is no intersection; we could also say this is an empty range: 1...1 )

Try something like this

require 'date'
sample = Date.parse('2011-01-01')
sample1 = Date.parse('2011-01-15')
sample2 = Date.parse('2010-12-19')
sample3 = Date.parse('2011-01-11')

puts (sample..sample1).to_a & (sample2..sample3).to_a

What this will give you is a array of intersection dates!!

I have times as [[start, end], ...] and I want to remove the some time ranges from a each initial time range, here is what I did:

def exclude_intersecting_time_ranges(initial_times, other_times)
  initial_times.map { |initial_time|
    other_times.each do |other_time|
      next unless initial_time
      # Other started after initial ended
      next if other_time.first >= initial_time.last
      # Other ended before initial started
      next if other_time.last <= initial_time.first

      # if other time started before and ended after after, no hour is counted
      if other_time.first <= initial_time.first && other_time.last >= initial_time.last
        initial_time = nil
      # if other time range is inside initial time range, split in two time ranges
      elsif initial_time.first < other_time.first && initial_time.last > other_time.last
        initial_times.push([other_time.last, initial_time.last])
        initial_time = [initial_time.first, other_time.first]
      # if start time of other time range is before initial time range
      elsif other_time.first <= initial_time.first
        initial_time = [other_time.last, initial_time.last]
      # if end time of other time range if after initial time range
      elsif other_time.last >= initial_time.last
        initial_time = [initial_time.first, other_time.first]
      end
    end

    initial_time
  }.compact
end

Since this question is related to How to combine overlapping time ranges (time ranges union), I also wanted to post my finding of the gem range_operators here, because if helped me in the same situation.

I'd transfer them into an array, since arrays know the intersection-operation:

(Date.new(2011,1,1)..Date.new(2011,1,15)).to_a & (Date.new(2011,1,10)..Date.new(2011,2,15)).to_a

Of course this returns an Array. So if you want an Enumerator (Range doesn't seem to be possible since these are not consecutive values anymore) just throw to_enum at the end.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!