Skip to content

Instantly share code, notes, and snippets.

@mrbongiolo
Last active June 17, 2016 18:23
Show Gist options
  • Save mrbongiolo/4eb824205376b63e122c8ffee552e2a4 to your computer and use it in GitHub Desktop.
Save mrbongiolo/4eb824205376b63e122c8ffee552e2a4 to your computer and use it in GitHub Desktop.
A horrendous approach to "manage" Time Ranges in ruby.
class ManageableTimeRange < Range
def <=>(other)
leg_min(other)
end
def leg_min(other)
return -1 if self.min < other.min
return 0 if self.min == other.min
return 1
end
def leg_max(other)
return -1 if self.max < other.max
return 0 if self.max == other.max
return 1
end
def leg_both(other)
case double_spaceship_from_hell = [ leg_min(other), leg_max(other) ]
when [-1, -1], [1, 1]
return nil if self.max < other.min || self.min > other.max
end
double_spaceship_from_hell
end
def intersect?(other)
leg_both(other) ? true : false
end
def union(other)
case leg_both(other)
when [0, 0], [0, 1], [-1, 0], [-1, 1]
return self
when [0, -1], [1, 0], [1, -1]
return other
when [-1, -1], [1, 1]
return ManageableTimeRange.new([self.min, other.min].min,
[self.max, other.max].max)
end
end
alias_method :|, :union
def intersection(other)
case leg_both(other)
when [0, 0], [0, -1], [1, 0], [1, -1]
return self
when [-1, 1]
return other
when [0, 1], [1, 1]
return ManageableTimeRange.new(self.min, other.max)
when [-1, 0], [-1, -1]
return ManageableTimeRange.new(other.min, self.max)
end
end
alias_method :&, :intersection
def exclusion(other)
case leg_both(other)
when [0, -1], [0, 1]
return ManageableTimeRange.new([self.max, other.max].min,
[other.max, self.max].max)
when [-1, 0], [1, 0]
return ManageableTimeRange.new([self.min, other.min].min,
[other.min, self.min].max)
when [1, -1], [-1, 1], [1, 1],[-1,-1]
return [ ManageableTimeRange.new([self.min, other.min].min,
[other.min, self.min].max),
ManageableTimeRange.new([self.max, other.max].min,
[other.max, self.max].max) ]
end
end
alias_method :/, :exclusion
def find_intersections(others)
self.class.union_all(others).map do |other|
self.intersection(other)
end.compact
end
def find_exclusions(others)
return [self] if others.empty?
unified_others = self.find_intersections(others)
exclusions = []
while exclusions.empty? && other = unified_others.shift do
exclusions = [self.exclusion(other)].flatten.compact
end
while other = unified_others.shift do
if new_exclusion = exclusions.last.exclusion(other)
case new_exclusion
when Array
exclusions[exclusions.length - 1] = new_exclusion[0]
exclusions << new_exclusion[1]
when ManageableTimeRange
exclusions[exclusions.length - 1] = new_exclusion
end
end
end
exclusions.compact
end
def self.union_all(others)
if others.any? { |other| !other.is_a?(ManageableTimeRange) }
others.map! { |other| convert_to_manageable(other) }
end
others.sort!
unified = [others.shift]
while other = others.shift do
if new_union = unified.last.union(other)
unified[unified.length - 1] = new_union
else
unified << other
end
end
unified.compact
end
def self.convert_to_manageable(other)
new(other.begin, other.end)
end
end
require 'spec_helper'
require_relative 'manageable_time_range'
RSpec.describe ManageableTimeRange do
# Hours
let(:zero) { Time.new(2016,1,1,0,0,0) }
let(:one) { Time.new(2016,1,1,1,0,0) }
let(:two) { Time.new(2016,1,1,2,0,0) }
let(:three) { Time.new(2016,1,1,3,0,0) }
let(:four) { Time.new(2016,1,1,4,0,0) }
let(:five) { Time.new(2016,1,1,5,0,0) }
let(:six) { Time.new(2016,1,1,6,0,0) }
let(:seven) { Time.new(2016,1,1,7,0,0) }
let(:eight) { Time.new(2016,1,1,8,0,0) }
let(:nine) { Time.new(2016,1,1,9,0,0) }
let(:ten) { Time.new(2016,1,1,10,0,0) }
let(:eleven) { Time.new(2016,1,1,11,0,0) }
let(:twelve) { Time.new(2016,1,1,12,0,0) }
let(:thirteen) { Time.new(2016,1,1,13,0,0) }
let(:fourteen) { Time.new(2016,1,1,14,0,0) }
let(:fifteen) { Time.new(2016,1,1,15,0,0) }
let(:sixteen) { Time.new(2016,1,1,16,0,0) }
let(:seventeen) { Time.new(2016,1,1,17,0,0) }
let(:eighteen) { Time.new(2016,1,1,18,0,0) }
let(:nineteen) { Time.new(2016,1,1,19,0,0) }
let(:twenty) { Time.new(2016,1,1,20,0,0) }
let(:twenty_one) { Time.new(2016,1,1,21,0,0) }
let(:twenty_two) { Time.new(2016,1,1,22,0,0) }
let(:twenty_three) { Time.new(2016,1,1,23,0,0) }
let(:twenty_four) { Time.new(2016,1,1,24,0,0) }
# Ranges
let(:day_range) { described_class.new(zero, twenty_four) }
let(:commercial_hours) { described_class.new(six, eighteen) }
let(:two_to_three) { described_class.new(two, three) }
let(:three_to_six) { described_class.new(three, six) }
let(:seven_to_thirteen) { described_class.new(seven, thirteen) }
let(:seven_to_eleven) { described_class.new(seven, eleven) }
let(:eight_to_twelve) { described_class.new(eight, twelve) }
let(:other_eight_to_twelve) { described_class.new(eight, twelve) }
let(:eight_to_fourteen) { described_class.new(eight, fourteen) }
let(:eleven_to_fourteen) { described_class.new(eleven, fourteen) }
let(:fifteen_to_twenty) { described_class.new(fifteen, twenty) }
let(:nineteen_to_twenty_two) { described_class.new(nineteen, twenty_two) }
let(:twenty_to_twenty_four) { described_class.new(twenty, twenty_four)}
it "initialize correctly" do
expect(eight_to_twelve).to be_a Miss::ManageableTimeRange
expect(eight_to_twelve).to eql eight..twelve
end
describe "#<=>" do
context "when self.min < other.min" do
it "return -1" do
expect(eight_to_twelve <=> eleven_to_fourteen).to eql -1
end
end
context "when self.min == other.min" do
it "return 0" do
expect(eight_to_twelve <=> eight_to_fourteen).to eql 0
end
end
context "when self.min > other.min" do
it "return +1" do
expect(fifteen_to_twenty <=> eight_to_twelve).to eql +1
end
end
end
describe "#leg_both" do
context "when self.min < other.min" do
context "and self.max < other.max" do
it "return [-1, -1] when they intersect" do
expect(eight_to_twelve.leg_both(eleven_to_fourteen))
.to eql [-1, -1]
end
it "return nil when they don't intersect" do
expect(seven_to_thirteen.leg_both(fifteen_to_twenty))
.to eql nil
end
end
context "and self.max == other.max" do
it "return [-1, 0]" do
expect(eight_to_fourteen.leg_both(eleven_to_fourteen))
.to eql [-1, 0]
end
end
context "and self.max > other.max" do
it "return [-1, 1]" do
expect(seven_to_thirteen.leg_both(eight_to_twelve))
.to eql [-1, 1]
end
end
end
context "when self.min == other.min" do
context "and self.max < other.max" do
it "return [0, -1]" do
expect(eight_to_twelve.leg_both(eight_to_fourteen))
.to eql [0, -1]
end
end
context "and self.max == other.max" do
it "return [0, 0]" do
expect(eight_to_twelve.leg_both(other_eight_to_twelve))
.to eql [0, 0]
end
end
context "and self.max > other.max" do
it "return [0, 1]" do
expect(eight_to_fourteen.leg_both(eight_to_twelve))
.to eql [0, 1]
end
end
end
context "when self.min > other.min" do
context "and self.max < other.max" do
it "return [1, -1]" do
expect(eight_to_twelve.leg_both(seven_to_thirteen))
.to eql [1, -1]
end
end
context "and self.max == other.max" do
it "return [1, 0]" do
expect(eleven_to_fourteen.leg_both(eight_to_fourteen))
.to eql [1, 0]
end
end
context "and self.max > other.max" do
it "return [1, 1] when they intersect" do
expect(eleven_to_fourteen.leg_both(eight_to_twelve))
.to eql [1, 1]
end
it "return nil when they don't intersect" do
expect(fifteen_to_twenty.leg_both(seven_to_thirteen))
.to eql nil
end
end
end
end
describe "#intersect?" do
it "is TRUE when has an intersection" do
expect(seven_to_thirteen.intersect?(eight_to_twelve)).to be_truthy
expect(seven_to_eleven.intersect?(eleven_to_fourteen)).to be_truthy
expect(eight_to_twelve.intersect?(eleven_to_fourteen)).to be_truthy
expect(eight_to_fourteen.intersect?(other_eight_to_twelve)).to be_truthy
end
it "is FALSE when has not an intersection" do
expect(fifteen_to_twenty.intersect?(seven_to_eleven)).to be_falsy
expect(eleven_to_fourteen.intersect?(fifteen_to_twenty)).to be_falsy
end
end
describe "#union" do
context "when they 'touch' each other" do
it "make them bigger" do
expect(seven_to_thirteen | eight_to_twelve)
.to eql seven..thirteen
expect(seven_to_eleven | eleven_to_fourteen)
.to eql seven..fourteen
expect(eight_to_twelve.union(eleven_to_fourteen))
.to eql eight..fourteen
expect(eight_to_fourteen.union(other_eight_to_twelve))
.to eql eight..fourteen
end
end
context "when they are too far away" do
it "just return a nil, null, nothing, niente, nada!" do
expect(fifteen_to_twenty | seven_to_eleven).to eql nil
expect(eleven_to_fourteen.union(fifteen_to_twenty)).to eql nil
expect(described_class.new(one, two - 1).union(two..three)).to eql nil
end
end
end
describe "#intersection" do
context "when there is an intersection" do
it "return the god damn intersection" do
expect(seven_to_thirteen & eight_to_twelve)
.to eql eight..twelve
expect(seven_to_eleven & eleven_to_fourteen)
.to eql eleven..eleven
expect(eight_to_twelve.intersection(eleven_to_fourteen))
.to eql eleven..twelve
expect(eight_to_fourteen.intersection(other_eight_to_twelve))
.to eql eight..twelve
end
end
context "when there is no intersection to be be found" do
it "just return a nil, null, nothing, niente, nada!" do
expect(fifteen_to_twenty & seven_to_eleven).to eql nil
expect(eleven_to_fourteen.intersection(fifteen_to_twenty)).to eql nil
end
end
end
describe "#exclusion" do
context "when they overlap" do
it "return it" do
expect(seven_to_thirteen / eight_to_twelve)
.to eql [seven..eight, twelve..thirteen]
expect(seven_to_eleven / eleven_to_fourteen)
.to eql [seven..eleven, eleven..fourteen]
expect(eight_to_twelve.exclusion(eleven_to_fourteen))
.to eql [eight..eleven, twelve..fourteen]
expect(eight_to_fourteen.exclusion(other_eight_to_twelve))
.to eql twelve..fourteen
expect(eleven_to_fourteen / eight_to_fourteen)
.to eql eight..eleven
expect(three_to_six.exclusion(three_to_six))
.to eql nil
end
end
context "when they don't overlap" do
it "just return a nil, null, nothing, niente, nada!" do
expect(fifteen_to_twenty / seven_to_eleven).to eql nil
expect(eleven_to_fourteen.exclusion(fifteen_to_twenty)).to eql nil
end
end
end
describe "#find_intersections" do
it "return the correct array" do
expect(day_range.find_intersections([seven_to_eleven,
nineteen_to_twenty_two]))
.to eql [seven..eleven, nineteen..twenty_two]
expect(day_range.find_intersections([seven..eleven, fourteen..eighteen]))
.to eql [seven..eleven, fourteen..eighteen]
expect(commercial_hours.find_intersections([one..two,
twelve..sixteen,
twenty..twenty_three,
five..seven,
nine..thirteen,
seventeen..nineteen]))
.to eql [six..seven, nine..sixteen, seventeen..eighteen]
expect(commercial_hours.find_intersections([zero..five,
nineteen..twenty_four]))
.to eql []
expect(commercial_hours.find_intersections([]))
.to eql []
end
end
describe "#find_exclusions" do
it "return the correct array" do
expect(day_range.find_exclusions([seven_to_eleven,
nineteen_to_twenty_two]))
.to eql [zero..seven, eleven..nineteen, twenty_two..twenty_four]
expect(day_range.find_exclusions([seven..eleven, fourteen..twenty_four]))
.to eql [zero..seven, eleven..fourteen]
expect(commercial_hours.find_exclusions([one..two,
twelve..sixteen,
twenty..twenty_three,
five..seven,
nine..thirteen,
seventeen..nineteen]))
.to eql [seven..nine, sixteen..seventeen]
expect(commercial_hours.find_exclusions([ten..eleven]))
.to eql [six..ten, eleven..eighteen]
expect(commercial_hours.find_exclusions([six..twelve,
six..eight,
seven..fourteen,
nine..sixteen,
sixteen..seventeen]))
.to eql [seventeen..eighteen]
expect(commercial_hours.find_exclusions([seven..nine,
fifteen..eighteen]))
.to eql [six..seven, nine..fifteen]
expect(commercial_hours.find_exclusions([six..twelve,
twelve..eighteen]))
.to eql []
expect(commercial_hours.find_exclusions([]))
.to eql [six..eighteen]
end
end
describe ".union_all" do
it "return an array with all possible unions" do
expect(described_class.union_all([seven..eleven,
nineteen..twenty_two]))
.to eql [seven..eleven, nineteen..twenty_two]
expect(described_class.union_all([eight_to_twelve, seven_to_thirteen]))
.to eql [seven..thirteen]
expect(described_class.union_all([eight_to_twelve,
eleven_to_fourteen,
eight_to_fourteen]))
.to eql [eight..fourteen]
expect(described_class.union_all([nineteen_to_twenty_two,
seven_to_eleven,
eight_to_twelve,
fifteen_to_twenty]))
.to eql [seven..twelve, fifteen..twenty_two]
expect(described_class.union_all([two_to_three,
three_to_six,
seven_to_eleven,
fifteen_to_twenty,
twenty_to_twenty_four]))
.to eql [two..six, seven..eleven, fifteen..twenty_four]
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment