一种有效的方法来找出Ruby中一个时段和一组范围的差异

时间:2016-12-22 18:21:02

标签: ruby algorithm

我在Ruby中有很多时间范围:

period = Time.parse('8:00am')..Time.parse('8:00pm')
incidents = [
  Time.parse('7:00am')..Time.parse('9:00am'),
  Time.parse('1:00pm')..Time.parse('3:00pm'),
  Time.parse('1:30pm')..Time.parse('3:30pm'),
  Time.parse('7:00pm')..Time.parse('9:00pm'),
]

我试图在这段时间内获得一系列事件空闲块。对于上述内容,应该是:

[
  Time.parse('9:00am')..Time.parse('1:00pm')
  Time.parse('3:30pm')..Time.parse('7:00pm')
]

根据上述情况,事件可能会重叠或延伸到期间之外。范围或类似的任何操作是否会使这种计算变得更简单?

2 个答案:

答案 0 :(得分:3)

可能的解决方案

使用此range operator gem,此脚本(几乎)将返回您想要的内容。

period开头,并逐个减去每个incident

由于从另一个范围中减去一个范围可能会产生两个范围,因此该脚本实际上以[period]开头,并在迭代之间保留一系列免费事件范围:

require 'range_operators'

incident_free = incidents.inject([period]) do |free_ranges, incident|
  free_ranges.flat_map do |free_range|
    free_range - incident
  end
end

p incident_free
#=> [2016-12-22 09:00:01 +0100..2016-12-22 12:59:59 +0100, 2016-12-22 15:30:01 +0100..2016-12-22 18:59:59 +0100]

注释

它抱怨Time#succ已经过时了。您可以添加

class Time
  def succ
    self+1
  end
end

删除弃用警告,或使用Gemfile:

gem 'range_operators', :git => 'https://github.com/monocle/range_operators.git'

安装较新版本的gem,并修复Time

该脚本的分辨率为1秒,第一个范围从09:00:01开始,因为在09:00:00之前发生了一次事件。

答案 1 :(得分:2)

full_range为范围,ranges为范围数组,分别代表提问者称为periodincidents的内容。我假设所有范围中包含的元素都可以是任何对象,前提是它们可以与适用的类别进行比较。方法<=>以及模块Comparableinclude d。

<强>代码

def uncovered_ranges(full_range, ranges)
  ucrs = [full_range]
  ranges.each do |r|
    ucrs = ucrs.flat_map do |ucr|
      if ucr.first >= r.last || ucr.last <= r.first
        ucr 
      elsif r.first <= ucr.first && r.last >= ucr.last
        nil
      elsif r.first <= ucr.first && r.last < ucr.last
        r.last..ucr.last
      elsif r.first > ucr.first && r.last >= ucr.last
        ucr.first..r.first
      else
        [ucr.first..r.first, r.last..ucr.last]
      end
    end.compact
  end
  ucrs
end

<强>实施例

full_range = 1..20
ranges = [3..4, 6..8, 10..12, 8..14, 16..17, 20..20]   

uncovered_ranges(full_range, ranges)
  #=> [1..3, 4..6, 14..16, 17..20]

require 'time'

full_range = Time.parse('8:00am')..Time.parse('8:00pm')
  #=> 2016-12-22 08:00:00 -0800..2016-12-22 20:00:00 -0800 

ranges = [
  Time.parse('7:00am')..Time.parse('9:00am'),
  Time.parse('1:00pm')..Time.parse('3:00pm'),
  Time.parse('1:30pm')..Time.parse('3:30pm'),
  Time.parse('7:00pm')..Time.parse('9:00pm'),
]
  #=> [2016-12-22 07:00:00 -0800..2016-12-22 09:00:00 -0800,
  #    2016-12-22 13:00:00 -0800..2016-12-22 15:00:00 -0800,
  #    2016-12-22 13:30:00 -0800..2016-12-22 15:30:00 -0800,
  #    2016-12-22 19:00:00 -0800..2016-12-22 21:00:00 -0800] 

uncovered_ranges(full_range, ranges)
  #=> [2016-12-22 09:00:00 -0800..2016-12-22 13:00:00 -0800,
  #    2016-12-22 15:30:00 -0800..2016-12-22 19:00:00 -0800]  

<强>解释

对我来说,解释发生了什么的最简单也是最简单的方法是插入一些puts语句并运行上面第一个例子的代码。

def uncovered_ranges(full_range, ranges)
  ucrs = [full_range]
  puts "ucrs initially=#{ucrs}"
  ranges.each do |r|
    puts "\ncovering range r=#{r}"
    ucrs = ucrs.flat_map do |ucr|
      puts "  range uncovered so far ucr=#{ucr}"
      if ucr.first >= r.last || ucr.last <= r.first
        puts "  in if #1, returning #{ucr}"
        ucr 
      elsif r.first <= ucr.first && r.last >= ucr.last
        puts "  in if #2, returning nil"
        nil
      elsif r.first <= ucr.first && r.last < ucr.last
        puts "  in if #3, returning #{r.last..ucr.last}"
        r.last..ucr.last
      elsif r.first > ucr.first && r.last >= ucr.last
        puts "  in if #4, returning #{ucr.first..r.first}"
        ucr.first..r.first
      else
        puts "  in else, returning #{[ucr.first..r.first, r.last..ucr.last]}"
       [ucr.first..r.first, r.last..ucr.last]
      end
    end.tap { |u| puts "ucrs after processing range #{r}=#{u}" }.
        compact.
        tap { |u| puts "ucrs after compact=#{u}" }
  end
  ucrs
end

uncovered_ranges 1..20, [3..4, 6..8, 10..12, 8..14, 16..17, 20..20]

打印以下内容。

ucrs initially=[1..20]

covering range r=3..4
  range uncovered so far ucr=1..20
  in else, returning [1..3, 4..20]
ucrs after processing range 3..4=[1..3, 4..20]
ucrs after compact=[1..3, 4..20]

covering range r=6..8
  range uncovered so far ucr=1..3
  in if #1, returning 1..3
  range uncovered so far ucr=4..20
  in else, returning [4..6, 8..20]
ucrs after processing range 6..8=[1..3, 4..6, 8..20]
ucrs after compact=[1..3, 4..6, 8..20]

covering range r=10..12
  range uncovered so far ucr=1..3
  in if #1, returning 1..3
  range uncovered so far ucr=4..6
  in if #1, returning 4..6
  range uncovered so far ucr=8..20
  in else, returning [8..10, 12..20]
ucrs after processing range 10..12=[1..3, 4..6, 8..10, 12..20]
ucrs after compact=[1..3, 4..6, 8..10, 12..20]

covering range r=8..14
  range uncovered so far ucr=1..3
  in if #1, returning 1..3
  range uncovered so far ucr=4..6
  in if #1, returning 4..6
  range uncovered so far ucr=8..10
  in if #2, returning nil
  range uncovered so far ucr=12..20
  in if #3, returning 14..20
ucrs after processing range 8..14=[1..3, 4..6, nil, 14..20]
ucrs after compact=[1..3, 4..6, 14..20]

covering range r=16..17
  range uncovered so far ucr=1..3
  in if #1, returning 1..3
  range uncovered so far ucr=4..6
  in if #1, returning 4..6
  range uncovered so far ucr=14..20
  in else, returning [14..16, 17..20]
ucrs after processing range 16..17=[1..3, 4..6, 14..16, 17..20]
ucrs after compact=[1..3, 4..6, 14..16, 17..20]

covering range r=20..20
  range uncovered so far ucr=1..3
  in if #1, returning 1..3
  range uncovered so far ucr=4..6
  in if #1, returning 4..6
  range uncovered so far ucr=14..16
  in if #1, returning 14..16
  range uncovered so far ucr=17..20
  in if #1, returning 17..20
ucrs after processing range 20..20=[1..3, 4..6, 14..16, 17..20]
ucrs after compact=[1..3, 4..6, 14..16, 17..20]
  #=> [1..3, 4..6, 14..16, 17..20]