可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
In ruby, given two date ranges, I want the range that represents the intersection of the two date ranges, or nil if no intersection. For example:
(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
Edit: Should have said that I want it to work for DateTime as well, so interval can be down to mins and secs:
(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
回答1:
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)>
回答2:
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
回答3:
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?
回答4:
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 )
回答5:
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!!
回答6:
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
回答7:
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.
回答8:
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.