Skip to content

Instantly share code, notes, and snippets.

@br3nt
Created March 28, 2015 04:39
Show Gist options
  • Save br3nt/67fca3da7958632c619d to your computer and use it in GitHub Desktop.
Save br3nt/67fca3da7958632c619d to your computer and use it in GitHub Desktop.
Domain for constraint programming
class Domain
INFINITY = 1.0 / 0.0
attr_reader :domain
# TODO:
# methods: <<, +, &, -
def initialize(*ranges)
ranges = standardise(ranges)
ranges = merge_ranges(ranges)
@domain = ranges
end
def remove(*range)
range = standardise(range)
remove_range(range, @domain)
end
def min
@domain.collect(&:min).min
end
def max
@domain.collect(&:max).max
end
def overlaps?(*ranges)
!!@domain.find {|r1| standardise(ranges).find {|r2| r1.intersects?(r2) } }
end
def distinct?(*ranges)
!overlaps?(ranges)
end
def standardise(ranges)
case ranges
when Fixnum
[(ranges)..(ranges)]
when Array
ranges.collect {|r| standardise(r) }.flatten
when Range
[ranges]
when self.class
ranges.domain
else
raise "Unsupported type: #{ranges.inspect}"
end
end
# TODO: remove - this is what union should be
def merge_ranges(ranges)
temp = ranges.dup
ranges.each do |r1|
overlapping = temp.select {|r2| r1.intersects?(r2) }
overlapping.each {|r2| temp.delete(r2) }
min = overlapping.collect(&:min).min
max = overlapping.collect(&:max).max
temp << ( min..max )
end
temp
end
def remove_range(range, domain)
# call this method recursively
case range
when Array
range.collect {|r| remove_range(r, domain) }
when Range
remove_singular_range(range, domain)
else
# not supported
end
end
# TODO: remove - this is sort of done with `#difference`
def remove_singular_range(range, domain)
matches = domain.select {|r| r.intersects?(range) }
matches.each do |match|
domain.delete(match)
domain << match - range
end
domain.flatten
end
# TODO: this needs to be fixed
def intersection(*ranges)
standardise(ranges).inject(domain.dup) do |temp, r2|
overlapping = temp.select {|r| r.intersects?(r2) }
temp = temp - overlapping
temp << overlapping.collect{|r1| r1 & r2 }
temp.flatten!
end
end
alias_method :&, :intersection
# TODO: this needs to be fixed
def union(*ranges)
standardise(ranges).inject(domain.dup) do |temp, r2|
overlapping = temp.select {|r| r.intersects?(r2) }
temp = temp - overlapping
temp << overlapping.collect{|r1| r1 + r2 }
temp.flatten!
end
end
alias_method :+, :union
def difference(*ranges)
standardise(ranges).inject(domain.dup) do |temp, r2|
overlapping = temp.select {|r| r.intersects?(r2) }
temp = temp - overlapping
temp << overlapping.collect{|r1| r1 - r2 }
temp.flatten!
end
end
alias_method :-, :difference
end
class Range
def intersects?(r2)
!!(self.min && r2.min) && (self.min <= r2.max) && (self.max >= r2.min)
end
def intersection(r2)
if intersects?(r2)
( [self.min, r2.min].max )..( [self.max, r2.max].min )
end
end
alias_method :&, :intersection
def union(r2)
if intersects?(r2)
( [self.min, r2.min].min )..( [self.max, r2.max].max )
end
end
alias_method :+, :union
def subset_of?(r2)
!!(self.min && r2.min) && (self.min >= r2.min) && (self.max <= r2.max)
end
def difference(r2)
return if subset_of?(r2)
if intersects?(r2)
domain = []
domain << ( (self.min)...(r2.min) ) if self.min < r2.min
domain << ( (r2.max.next)..(self.max) ) if self.max > r2.max
domain.count == 1 ? domain.first : domain
else
self
end
end
alias_method :-, :difference
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment