Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficient Way to Find the Difference of a Period and Set of Ranges in Ruby

Tags:

algorithm

ruby

I've got a number of time ranges in 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'),
]

I'm trying to get an array of incident free blocks within the period. For the above that'd be:

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

From the above it is possible for incidents to overlap or extend outside the period. Do any operations exist on range or something similar that would make this sort of calculation simpler?

like image 471
Stussa Avatar asked Dec 22 '16 18:12

Stussa


1 Answers

Possible solution

Using this range operator gem, this script would (almost) return what you want.

It begins with period, and substracts every incident one after the other.

Since substracting a range from another can result in two ranges, the script actually begins with [period] and keeps an array of free incident ranges between iterations :

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]

Notes

It complains that Time#succ is obsolete. You can either add

class Time
  def succ
    self+1
  end
end

to remove the deprecation warnings, or use a Gemfile with :

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

to install a newer version of the gem, with a fix for Time.

The script works with a resolution of 1 second, and the first range begins at 09:00:01 because there was an incident until 09:00:00.

like image 166
Eric Duminil Avatar answered Oct 11 '22 23:10

Eric Duminil