使用日期间隔数组重叠日期

时间:2017-01-13 11:59:00

标签: ruby intervals

给定一系列日期范围:

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

3 个答案:

答案 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