Ruby:两个范围之间的交集

在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 

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

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

 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 

我发现了这个: 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。

谁有更好的解决方案?

我为升序范围烘焙了这个解决方案,同时也考虑了排除结束的情况:

 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)

尝试这样的事情

 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 

这将给你一个交叉日期!

我有[[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 

由于这个问题与如何组合重叠时间范围(时间范围联合)有关 ,我还想在这里发布我对gem range_operators的发现,因为如果在同样的情况下帮助我。

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

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

当然这会返回一个数组。 因此,如果你想要一个Enumerator(Range似乎不可能,因为它们不再是连续的值),只需在结尾处抛出to_enum