【问题标题】:How to find if range is contained in an array of ranges?如何查找范围是否包含在范围数组中?
【发布时间】:2010-12-09 05:14:30
【问题描述】:

例子

business_hours['monday'] = [800..1200, 1300..1700]
business_hours['tuesday'] = [900..1100, 1300..1700]

...

然后我有一堆事件占据了其中一些间隔,例如

event = { start_at: somedatetime, end_at: somedatetime }

从某个日期到某个日期迭代事件,我创建另一个数组

busy_hours['monday'] = [800..830, 1400..1415]

...

现在我的挑战是

  • 创建包含 business_hours 减去 busy_hours 的 available_hours 数组

available_hours = business_hours - busy_hours

  • 给定特定的持续时间,比如 30 分钟,在 available_hours 中找出可用的时间段。在上面的例子中,这样的方法会返回

available_slots['monday'] = [830..900, 845..915, 900..930, and so on]

并不是它以 15 分钟为增量检查 available_hours 是否有指定持续时间的时隙。

感谢您的帮助!

【问题讨论】:

  • 这个问题就是调度软件要花很多钱的原因:)
  • 我希望我可以使用一些现有的库,但什么也没找到。希望一些 SOers 同胞能够有所启发。
  • 看起来你可以扩展 Range 做你想做的事,但这里的问题是你使用 base-10 表示 base-60 值。 900 - 830 = 70,而不是 30。
  • 好点。我应该使用不同的对象而不是 Ranges 吗?或者像你说的扩展 Range 并将算术运算符更改为使用 base 60?
  • 我想您可能会发现,如果您的代码在内部将一天中的时间表示为 0-95,以 15 分钟为单位,然后您只能将其转换为人类可读的形式,那么您的生活会更轻松用于显示和输入。

标签: ruby-on-rails ruby time calendar range


【解决方案1】:

我认为这是位域的工作。不幸的是,这个解决方案将依赖于幻数、转换助手和相当多的二进制逻辑,所以它不会很漂亮。但它会起作用并且非常有效。

这就是我解决问题的方式:

将您的日子分解为合理的时间间隔。我将按照您的示例并将每个 15 分钟的时间块视为一个时间块(主要是因为它使示例保持简单)。然后将您每小时的可用性表示为十六进制数字。

例子:

  • 0xF = 0x1111 => 整小时可用。
  • 0xC = 0x1100 => 前半小时可用。

将其中的 24 个字符串连在一起代表一天。如果您可以确定不会发生超出范围的事件,则更少。该示例继续假设 24 小时。

为了便于阅读,我将长十六进制数字拆分为单词 假设这一天从 00:00 到 23:59 business_hours['monday'] = 0x0000 0000 FFFF 0FFF F000 0000

要获得busy_hours,您可以以类似的格式存储事件,并将它们全部放在一起。

示例:

event_a = 0x0000 0000 00F0 0000 0000 0000 # 10:00 - 11:00 
event_b = 0x0000 0000 0000 07F8 0000 0000 # 13:15 - 15:15

busy_hours = event_a & event_b 

您可以从 busy_hours 和 business_hours 获取可用时间:

available_hours = business_hours & (busy_hours ^ 0xFFFF FFFF FFFF FFFF FFFF FFFF)

xor(^) 本质上将busy_hours 转换为not_busy_hours。 Anding (&) not_busy_hours 和 business_hours 为我们提供了当天的可用时间。

此方案还可以轻松比较许多人的可用时间。

all_available_hours = person_a_available_hours & person_b_available_hours & person_c_available_hours

然后找到适合可用时间的时间段。你需要做这样的事情: 将您的时间长度转换为与一个小时类似的十六进制数字,其中这些数字代表该时间段将涵盖的该小时的所有时间块。接下来将数字右移,这样就没有尾随的 0。

例子胜于解释: 0x1 => 15 分钟,0x3 => 半小时,0x7 => 45 分钟,0xF => 整小时,... 0xFF => 2 小时等

一旦你完成了,你就这样做:

acceptable_times =[]
(0 .. 24 * 4 - (#of time chunks time slot)).each do |i|
  acceptable_times.unshift(time_slot_in_hex) if available_hours & (time_slot_in_hex << i) == time_slot_in_hex << i
end

范围的高端有点混乱。所以让我们多看一点。我们不想改变太多次,否则我们可能会在早期阶段开始得到误报。

24 * 4 一天 24 小时,每个用 4 位表示。 - (#of time chunks in time slot) 在我们正在寻找的时间段内每 15 分钟减去 1 次检查。这个值可以通过(Math.log(time_slot_in_hex)/Math.log(2)).floor + 1找到

从一天结束时开始,检查每个时间段,在每次迭代中提前一个时间块(在本例中为 15 分钟)。如果时间段可用,则将其添加到可接受时间的开始。因此,当流程完成时,acceptable_times 会按照发生的顺序进行排序。

很酷的一点是,此实施允许合并时间段,以便您的与会者可以在一天中有一个忙碌的时段,将您正在寻找的时间段平分为休息时间,否则他们可能会很忙。

您可以编写在范围数组(即:[800..1200, 1300..1700])和十六进制表示之间转换的辅助函数。最好的方法是将行为封装在对象中并使用自定义访问器方法。然后使用相同的对象来表示日期、事件、繁忙时间等。唯一没有内置到这个方案中的是如何安排事件,以便它们可以跨越天的边界。

【讨论】:

  • 非常感谢您的帮助。你有汇编的背景吗? ;) 那些人喜欢有点逻辑!我将尝试实施此解决方案。你的邮箱是什么?如果我得到这个工作,我会回复你。还将尝试在 github 上提供此功能。
  • 没有汇编程序背景。我只是对算法二进制逻辑有足够的了解,知道什么时候下降到这样的基本级别是有用的。
【解决方案2】:

要回答问题的标题,请查找数组范围是否包含范围:

ary = [800..1200, 1300..1700]

test = 800..830
p ary.any? {|rng| rng.include?(test.first) and rng.include?(test.last)}
# => true

test = 1245..1330
p ary.any? {|rng| rng.include?(test.first) and rng.include?(test.last)}
# => false

可以写成

class Range
  def include_range?(r)
    self.include?(r.first) and self.include?(r.last)
  end
end

【讨论】:

【解决方案3】:

好的,我没有时间写一个完整的解决方案,但这个问题对我来说似乎并不难。我将以下原始方法拼凑在一起,您可以使用这些方法来帮助构建您的解决方案(您可能想要继承 Range 而不是猴子补丁,但这会给您提供思路):

class Range
  def contains(range)
    first <= range.first || last >= range.last
  end

  def -(range)
    out = []
    unless range.first <= first && range.last >= last
      out << Range.new(first, range.first) if range.first > first
      out << Range.new(range.last, last) if range.last < last
    end
    out
  end
end

您可以遍历工作时间并找到包含该事件的事件,如下所示:

event_range = event.start_time..event.end_time
matching_range = business_hours.find{|r| r.contains(event_range)}    

你可以这样构造新数组(伪代码,未测试):

available_hours = business_hours.dup
available_hours.delete(matching_range)
available_hours += matching_range - event_range

这应该是一种非常可重用的方法。当然,对于你的问题的下一部分,你需要一些完全不同的东西,但这就是我所有的时间:)

【讨论】:

  • 嗨 dasil003。谢谢回复。这些 Range 方法看起来很出色。会把它们放在我的标准库中。确实,第二部分很棘手。不过看看 EmFi 的解决方案。真的很出色。
  • Range.contains 不像你想象的那样工作。那||应该是 &&
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
  • 2014-12-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多