Ruby:两个范围之间的交集

时间:2011-12-07 11:27:09

标签: ruby date range

在ruby中,给定两个日期范围,我想要表示两个日期范围交集的范围,如果没有交集,则需要nil。例如:

(Date.new(2011,1,1)..Date.new(2011,1,15)) & (Date.new(2011,1,10)..Date.new(2011,2,15))
=> Mon, 10 Jan 2011..Sat, 15 Jan 2011

编辑:应该说我希望它也适用于DateTime,所以间隔可以缩短到分钟和秒:

(DateTime.new(2011,1,1,22,45)..Date.new(2011,2,15)) & (Date.new(2011,1,1)..Date.new(2011,2,15))
=> Sat, 01 Jan 2011 22:45:00 +0000..Tue, 15 Feb 2011

9 个答案:

答案 0 :(得分:23)

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)>

答案 1 :(得分:7)

您可以尝试这样做以获得表示交叉点的范围

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

转换您的示例:

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

答案 2 :(得分:2)

我为解决范围提升了这个解决方案,同时也处理了排除结束的情况:

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

我也有点担心你们将它添加到Range类本身,因为交叉范围的行为没有统一定义。 (如果相交1 ... 4和4 ... 1?为什么没有交叉时为零;我们也可以说这是一个空范围:1 ... 1)

答案 3 :(得分:1)

我发现了这个:http://www.postal-code.com/binarycode/2009/06/06/better-range-intersection-in-ruby/这是一个非常好的开始,但不适用于约会。我已经对此进行了一些调整:

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

我省略了测试,但它们基本上是上面更改日期的帖子中的测试。这适用于ruby 1.9.2。

任何人都有更好的解决方案吗?

答案 4 :(得分:0)

尝试这样的事情

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

这将给你一个交叉日期数组!!

答案 5 :(得分:0)

我的时间为[[start, end], ...],我希望从每个初始时间范围中删除一些时间范围,这就是我所做的:

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

答案 6 :(得分:0)

由于这个问题与How to combine overlapping time ranges (time ranges union)有关,我还想在这里发布我对gem range_operators的发现,因为如果在同样的情况下帮助我。

答案 7 :(得分:0)

从 Rails v3 开始,您可以将 overlaps?Range 一起使用

# For dates, make sure you have the correct format
first_range = first_start.to_date..first_end.to_date
second_range = second_start.to_date..second_end.to_date

intersection = first_range.overlaps?(second_range) # => Boolean

# Example with numbers
(1..7).overlaps?(3..5) # => true

docs 中的更多详细信息

答案 8 :(得分:-1)

我将它们转移到一个数组中,因为数组知道交叉操作:

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

当然这会返回一个数组。因此,如果你想要一个枚举器(范围似乎不可能,因为它们不再是连续值),只需在最后抛出to_enum