给定范围的两个大阵......计算(日期)范围内的两个数组的交集红宝石
A = [0..23, 30..53, 60..83, 90..113]
B = [-Float::INFINITY..13, 25..33, 45..53, 65..73, 85..93]
当我做了logical conjuction ...
C = A.mask(B)
然后我预计
describe "Array#mask" do
it{expect(C = A.mask(B)).to eq([0..13, 30..33, 45..53, 65..73, 90..93])}
end
感觉像它应该是...
C = A & B
=> []
但这是空的because none of the ranges are identical。
下面是一个图例。
。
我已经在范围内包含Infinity,因为解决此问题typically involve converting the Range to an Array or Set。
我当前解决方案 这是我与测试通过的速度和准确性当前的解决方案。我正在寻找意见和/或建议的改进。第二项测试使用优秀的IceCube gem to generate an array of date ranges。在我的掩码方法中有一个隐含的假设,即每个时间表内的日期范围出现不重叠。
require 'pry'
require 'rspec'
require 'benchmark'
require 'chronic'
require 'ice_cube'
require 'active_support'
require 'active_support/core_ext/numeric'
require 'active_support/core_ext/date/calculations'
A = [0..23, 30..53, 60..83, 90..113]
B = [-Float::INFINITY..13, 25..33, 45..53, 65..73, 85..93]
class Array
def mask(other)
a_down = self.map{|r| [:a, r.max]}
a_up = self.map{|r| [:a, r.min]}
b_down = other.map{|r| [:b, r.max]}
b_up = other.map{|r| [:b, r.min]}
up = a_up + b_up
down = a_down + b_down
a, b, start, result = false, false, nil, []
ticks = (up + down).sort_by{|i| i[1]}
ticks.each do |tick|
tick[0] == :a ? a = !a : b = !b
result << (start..tick[1]) if !start.nil?
start = a & b ? tick[1] : nil
end
return result
end
end
describe "Array#mask" do
context "simple integer array" do
it{expect(C = A.mask(B)).to eq([0..13, 30..33, 45..53, 65..73, 90..93])}
end
context "larger date ranges from IceCube schedule" do
it "should take less than 0.1 seconds" do
year = Time.now..(Time.now + 52.weeks)
non_premium_schedule = IceCube::Schedule.new(Time.at(0)) do |s|
s.duration = 12.hours
s.add_recurrence_rule IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday, :thursday, :friday).hour_of_day(7).minute_of_hour(0)
end
rota_schedule = IceCube::Schedule.new(Time.at(0)) do |s|
s.duration = 7.hours
s.add_recurrence_rule IceCube::Rule.weekly(2).day(:tuesday).hour_of_day(15).minute_of_hour(30)
end
np = non_premium_schedule.occurrences_between(year.min, year.max).map{|d| d..d+non_premium_schedule.duration}
rt = rota_schedule.occurrences_between(year.min, year.max).map{|d| d..d+rota_schedule.duration}
expect(Benchmark.realtime{np.mask(rt)}).to be < 0.1
end
end
end
感觉很奇怪,你不能用Ruby的现有核心方法做到这一点?我错过了什么吗?我发现自己在相当经常的基础上计算范围交叉点。
我还发现,您可以使用相同的方法通过传递单个项目数组来找到两个单个范围之间的交集。例如
[(54..99)].mask[(65..120)]
我意识到我有种回答我自己的问题,但认为我会把它留在这里作为别人的参考。
感谢您的回答。不幸的是,当A和B的长度不同或者A的范围包含B中的多个范围时,它不起作用。数组的大小不同,因为我的实际用例是来自IceCube gem的调度。所以范围可能在一天,一个月,一周或一年中重复出现。在这个特殊情况下,我试图计算在非高级时间(周一至周五上午7点 - 下午7点)工作时间的工作时间。 –
P.S.有趣的是看到Array#zip方法。我以前没有使用或调查过。花了我一段时间,让我的头在实际上做了什么,直到我意识到它就像一个拉链交错的牙齿。 –
@KevinMonk,我做了一个小小的改变,产生你指定的输出... –