给定一系列日期范围:
[date_range1, date_range2, date_range3, date_range4, ...]
和日期范围如
given_date_range = (date_whithin_date_range1 .. date_between_date_range2_and_date_range3)
如何在数组中的范围元素之间返回未覆盖的间隔数组,这些范围在given_date_range
内?
对于这种情况,它应该返回
[[date_range1.end + 1, date_range2.begin - 1],
[date_range2.end + 1, date_between_date_range2_and_date_range3.end]]
具有实际值的示例:
given_date_range = (Date.new(2014, 7, 3) .. Date.new(2016, 3, 18))
array_of_date_ranges = [(Date.new(2014, 5, 10) .. Date.new(2014, 8, 10)),
(Date.new(2015, 3, 2) .. Date.new(2015, 4, 9)),
(Date.new(2016, 3, 5) .. Date.new(2016, 4, 8)),
(Date.new(2016, 6, 2) .. Date.new(2016, 7, 3)),
... ]
预期结果:
[[Date.new(2014, 8, 11), Date.new(2015, 3, 1)],
[Date.new(2015, 4, 10), Date.new(2016, 3, 4)]]
答案 0 :(得分:1)
由于范围没有太多元素,因此将它们转换为日期数组是可以接受的。
我们从date_ranges
中移除所有given_date_range
日期,以获取未覆盖日期数组。我们在非连续的日子里对这个数组进行切片,并将得到的日期数组数组转换为日期范围数组:
require 'date'
date_ranges = [(Date.new(2014, 5, 10) .. Date.new(2014, 8, 10)),
(Date.new(2015, 3, 2) .. Date.new(2015, 4, 9)),
(Date.new(2016, 3, 5) .. Date.new(2016, 4, 8)),
(Date.new(2016, 6, 2) .. Date.new(2016, 7, 3))]
given_date_range = (Date.new(2014, 7, 3) .. Date.new(2016, 3, 18))
uncovered_dates = given_date_range.to_a - date_ranges.flat_map(&:to_a)
puts uncovered_dates.sort
.slice_when { |d1, d2| d1 + 1 != d2 }
.map { |free_range| (free_range.first..free_range.last) }
#=>
# 2014-08-11..2015-03-01
# 2015-04-10..2016-03-04
您可以使用此gem在范围上添加算术运算。
从完整范围开始,逐个减去每个范围。
由于从另一个范围中减去一个范围可能会产生两个范围,因此该脚本实际上以[complete_range]
开头,并在迭代之间保留一系列范围:
require 'date'
require 'range_operators'
date_ranges = [(Date.new(2014, 5, 10) .. Date.new(2014, 8, 10)),
(Date.new(2015, 3, 2) .. Date.new(2015, 4, 9)),
(Date.new(2016, 3, 5) .. Date.new(2016, 4, 8)),
(Date.new(2016, 6, 2) .. Date.new(2016, 7, 3))]
#given_date_range = date_ranges.first.min .. date_ranges.last.max # assuming the ranges are sorted
given_date_range = (Date.new(2014, 7, 3) .. Date.new(2016, 3, 18))
uncovered = date_ranges.inject([given_date_range]) do |free_ranges, range|
free_ranges.flat_map do |free_range|
free_range - range
end
end
puts uncovered
# => 2014-08-11..2015-03-01
# 2015-04-10..2016-03-04
答案 1 :(得分:1)
having
答案 2 :(得分:0)
编辑:更新答案以反映有问题的变化。
到目前为止,我已经以最可读的方式解决了这个问题。 我没有使用日期,而是使用整数 - 它的工作原理相同。
# helper function
def discover_extras(two_ranges, given_range)
first = two_ranges.first
last = two_ranges.last
if given_range.cover?(first.end)
if given_range.cover?(last.begin)
first.end+1..last.begin-1
else
first.end+1..given_range.end if given_range.end > first.end
end
else
nil
end
end
def values_not_covered_in(ranges, given_range)
not_covered = []
ranges.each_cons(2) do |two_ranges|
extras = discover_extras(two_ranges, given_range)
not_covered.push(extras) if extras
end
not_covered
end
这是我用来验证所有内容的规范文件
require "dates_not_covered"
describe '#discover_extras(two_ranges, given_range)' do
it 'when before range returns nil' do
expect(discover_extras([2..10, 12..15], 0..1)).to be nil
end
it 'when covers first range returns nil' do
expect(discover_extras([2..10, 12..15], 2..10)).to be nil
end
it 'when includes both ranges returns between ranges' do
expect(discover_extras([2..10, 13..15], 2..15)).to eq(11..12)
end
it 'for [2..10, 12..15] and 2..11 returns 11..11' do
expect(discover_extras([2..10, 12..15], 2..11)).to eq(11..11)
end
end
describe '#values_not_covered_in(ranges, given_range)' do
it 'for [2..10, 12..15] and 2..10 returns []' do
expect(values_not_covered_in([2..10, 12..15], 2..10)).to eq []
end
it 'for [2..10, 12..15] and 2..11 returns [11..11]' do
expect(values_not_covered_in([2..10, 12..15], 2..11)).to eq([11..11])
end
it 'for [2..10, 12..15] and 2..15 returns [11..11]' do
expect(values_not_covered_in([2..10, 12..15], 2..15)).to eq([11..11])
end
end
再一次,这适用于整数和日期。 唯一的区别是我决定返回范围数组,而不是范围开始和结束的数组。我认为这样做会更好。
如果你坚持要返回数组,那么只需将这些范围转换为数组
not_covered.push([extras.begin, extras.eng]) if extras