Last active
June 17, 2016 18:23
-
-
Save mrbongiolo/4eb824205376b63e122c8ffee552e2a4 to your computer and use it in GitHub Desktop.
A horrendous approach to "manage" Time Ranges in ruby.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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