【问题标题】:Calculate the intersection of two arrays of ranges (of dates) in ruby计算ruby中两个范围(日期)数组的交集
【发布时间】:2015-01-12 10:33:29
【问题描述】:

给定两个大范围数组...

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

我的当前解决方案 这是我目前通过速度和准确性测试的解决方案。我正在寻找 cmets 和/或改进建议。第二个测试使用优秀的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)]

我意识到我已经回答了自己的问题,但我想我会把它留在这里作为其他人的参考。

【问题讨论】:

    标签: ruby range date-range ice-cube


    【解决方案1】:

    我不确定我是否真的理解你的问题;你的expect 声明让我有点困惑,我不知道为什么你的数组大小不一样。也就是说,如果你想计算两个范围的交集,我喜欢这个猴子补丁(来自Ruby: intersection between two ranges):

    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
    

    然后你可以这样做:

    A = [0..23, 30..53, 60..83, 0..0, 90..113]
    B = [-Float::INFINITY..13, 25..33, 45..53, 65..73, 85..93]
    
    A.zip(B).map { |x, y| x & y }
    # => [0..13, 30..33, nil, nil, 90..93]
    

    这似乎是一个合理的结果......

    编辑

    如果您按照上面发布的方式对Range 进行monkeypatch,然后执行以下操作:

    # your initial data
    A = [0..23, 30..53, 60..83, 90..113]
    B = [-Float::INFINITY..13, 25..33, 45..53, 65..73, 85..93]
    
    A.product(B).map {|x, y| x & y }.compact
    # => [0..13, 30..33, 45..53, 65..73, 90..93]
    

    您会得到您指定的结果。不知道它如何比较性能,我不确定排序顺序......

    【讨论】:

    • 感谢您的回答。不幸的是,当 A 和 B 的长度不同或 A 中的范围覆盖 B 中的多个范围时,它不起作用。数组的大小不同,因为我的实际用例是 IceCube gem 中的时间表。因此,这些范围可能会在一天、一个月、一周或一年内重复出现。在这种特殊情况下,我试图计算工作轮班在非优质时间(周一至周五早上 7 点至晚上 7 点)的工作时间。
    • P.S.有趣的是看到 Array#zip 方法。我以前没有使用或调查过这个。我花了一段时间才弄清楚它实际上做了什么,直到我意识到它就像拉链的交叉齿。
    • 这是一个很好的解决方案,但数组通常有 200 - 500 个元素长,因此产品数组的长度很容易达到 500^2 = 250k。不过我喜欢这个概念。
    • @KevinMonk 如果您担心将某些范围与可能的许多其他范围进行比较,那么听起来您并不是真的想要 Ranges 的交集。您可能会从考虑使用自定义类来表示这些 Range 值的集合的程序设计中受益,然后在此答案的基础上实现您在基本 Range 交集之外所需的较少不可知的功能。
    • @IronSavior 这是一个公平的观点。我同意。它是 Array 的一个特定“类”。 ScheduleArray 或 ArrayRange 或类似的东西。我接受过电子工程师培训,在硬件领域,这是一个非常简单的问题;这是一个双输入与门。这让我想知道是否有某种基于 IO 的解决方案,但我对编写它的 IO 对象没有足够的了解。
    猜你喜欢
    • 2022-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多