【问题标题】:Efficient Way to Find the Difference of a Period and Set of Ranges in Ruby在 Ruby 中查找周期和范围集差异的有效方法
【发布时间】:2017-05-08 10:54:28
【问题描述】:

我在 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')
]

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

【问题讨论】:

  • 如果这是一个实际应用程序并且您使用的是 PostgreSQL,您应该考虑通过 SQL 查询使用date math functionality,而不是在应用程序逻辑内部处理。
  • 如果您只是从算法开发的角度来看这个,this question/answer should give you some fodder for consderation
  • @coreyward 不幸的是,数据没有存储在 SQL 数据库中。我也不太清楚 SQL 版本是否会更容易基于链接(如果我要先导入 PSQL)。
  • @Stussa 关于我们在讨论的各个集合中有多少条目?
  • @Felix 每个周期需要检查五到十个事件

标签: ruby algorithm


【解决方案1】:

可能的解决方案

使用这个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 之前发生了事件。

【讨论】:

    【解决方案2】:

    full_range 是一个范围,ranges 是一个范围数组,分别代表提问者所说的periodincidents。我假设所有范围中包含的元素可以是任何对象,只要它们可以与适用类的方法<=> 进行比较,并且模块Comparable 一直是included。

    代码

    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] 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-05
      • 1970-01-01
      相关资源
      最近更新 更多