# Envelopes are useful when finding the boundaries of a set of instances. # They expand as new values are added, and keep track of how many items # have been added to construct them. module Eymiha # An EnvelopeException is generally raised when adding an object to an # instance that it does not know how to interpret, or when requesting # bounderies before any values have been added. class EnvelopeException < Exception end # A BaseEnvelope provides a means to keep and provide a count of objects # that have been added to an envelope. class BaseEnvelope # Called when requesting envelope bounderies before any values have been # added. def raise_no_envelope raise EnvelopeException, "No values are enveloped" end # Called when the value cannot be compared with the the boundaries of the # instance. def raise_no_compare(value=nil) value = "'#{value}' " unless value == nil raise EnvelopeException, "The value #{value}cannot be compared with the envelope" end # count of added values reader. attr_reader :count # Returns a new instance with no values added. def initialize @count = 0 end end # An Envelope is the minimum envelope that will completely contain a set of # values. Values may be added to an instance, and it can return the number # of values considered so far and its high and low boundaries. # # Envelopes can be used to generate ranges, however the result may be of # limited utility if the types of values being enveloped don't play nicely # with ranges. class Envelope < BaseEnvelope # Creates and returns an instance. If an argument is given, it is passed # to the set method to initialize the new instance. def initialize(value=nil) super() add(value) unless value == nil end # Returns a string representation of the instance. def to_s values = (count > 0)? "\n high #{high}\n low #{low}" : "" "Envelope: count #{count}#{values}" end # Adds a value to the instance. When # * x is an Envelope, it is coalesced into the instance. # * otherwise, the envelope is extened to contain the value. # * if the value cannot be compared to the boundaries, an EnvelopeException is raised. # The modified instance is returned. def add(value) if value.kind_of? Envelope count = value.count if (count > 0) add value.high add value.low @count += (count-2) end self else begin @high = value if (@count == 0 || value > @high) @low = value if (@count == 0 || value < @low) @count += 1 self rescue raise_no_compare value end end end # Returns the high boundary of the instance. # * if there are no boundaries, an EnvelopeException is raised. def high raise_no_envelope if @count == 0 @high end # Returns the low boundary of the instance. # * if there are no boundaries, an EnvelopeException is raised. def low raise_no_envelope if @count == 0 @low end # Returns true if the instance completely contains the argument: # * value is an Envelope, its high and low are contained. # * otherwise, the value is contained. # * if the value cannot be compared to the boundaries, an EnvelopeException is raised. def contains?(value) if value.kind_of? Envelope (contains? value.high) && (contains? value.low) else begin (value >= low) && (value <= high) rescue raise_no_compare value end end end alias === contains? # Returns an inclusive range from the low to high boundaries def to_range low..high end end end