module Rangeary::Util::HashInf

Module to extend a Hash class instance to hold infinities information

{Rangeary} needs to know what its own infinities are. This class holds the information.

It should have only 2 keys of :negative and :positive though it is not constrained at the class level.

Method {HashInf#status} returns a Hash containing status information about the main values (negative and positive infinities). Some related utility methods are also defined.

The default infinity values (for negative and positive) are false (NOT nil because nil can mean the border of a beginless/endless Range), whereas the default (uninitialized) status for it is nil.

Constants

STATUS_ORDERS

Order Array of the {#status} parameters (smaller means a higher priority). If :definite, it means it exists in a Range/Rangeary or is specified in an argument.

STATUS_PRIORITIES

Reverse Hash of {STATUS_ORDERS} to give the priority number for {#status}.

@example

STATUS_PRIORITIES[:definite]  # => 0
STATUS_PRIORITIES[nil]        # => 2

Attributes

status[W]

Method to overwrite Hash @status . See {#status}.

Public Class Methods

construct(infinities=nil, status: {}) click to toggle source

Altenative constructor

Usually you can construct an instance like:

myhs = { negative: false, positive: false }
myhs.extend HashInf
myhs.status = { negative: negative, positive: positive }

This constructor is a shortcut.

Note that the returned object is a different object from the argument, and so you are free to destructively modify it.

If the argument is a {HashInf}, the contents are copied.

@example

HashInf.construct()  # => <HashInf: {negative: false, positive: false}, status: {negative: nil, positive: nil}>
HashInf.construct({negative: false, positive: false}, status: {negative: :guessed})

@param infinities [Hash, NilClass] if given, make sure the Hash has 2 keys of :negative and :positive with relevant infinity values, e.g., Float::INFINITY or nil or else. @param status [Hash] Optional. 1 or 2 keys of :negative and :positive with values of {HashInf::STATUS_ORDERS}. @return [Hash] extended with {HashInf}

# File lib/rangeary/util/hash_inf.rb, line 57
def self.construct(infinities=nil, status: {})
  rethsinf = {negative: false, positive: false}
  rethsinf.merge!(infinities) if infinities
  rethsinf.extend HashInf
  rethsinf.status = {negative: nil, positive: nil}.merge(status)
  rethsinf.status.merge!(infinities.status) if infinities.respond_to? :status
  rethsinf.status.merge!(status)
  rethsinf
end

Public Instance Methods

definite?(posneg) click to toggle source

Return true if the infinity of the specified polarity is :definite

@param posneg [Symbol] either :positive or :negative

# File lib/rangeary/util/hash_inf.rb, line 190
def definite?(posneg)
  status_is_a?(:definite, posneg)
end
guessed?(posneg) click to toggle source

Return true if the infinity of the specified polarity is :guessed

@param posneg [Symbol] either :positive or :negative

# File lib/rangeary/util/hash_inf.rb, line 197
def guessed?(posneg)
  status_is_a?(:guessed, posneg)
end
inspect() click to toggle source

Alter +Hash#inspect+ (Note irb does not take this into account)

@return [String]

Calls superclass method
# File lib/rangeary/util/hash_inf.rb, line 226
def inspect
  sprintf "<Hash(Inf): %s, status: %s>", super, status
end
merge_hashinf(other, force: false) click to toggle source

merges another {HashInf} (or Hash) like +Hash#merge+, taking into account the status, and returns a new {HashInf}

If other is a (non-HashInf) Hash, it should have no more than 2 keys of :positive and :negative.

If the statuses {HashInf#status} are the same between self and other, the more extreme value is adopted. Therefore,

    rang = Rangeary(?a..?c, positive: ?z)
r = rang + Rangeary(?c..?f, positive: ?y)
r.infinities[:positive]  # => ?z  (NOT ?y)

If the old and new infinities are incomparable, the result is uncertain.

@param other [HashInf, Hash] if it is Hash, converted into HashInf. @param force [Boolean] if true, other always has a higher priority like +Hash#merge+

unless the corresponding status is +false+, in which case nothing changes.
This is useful to handle the parameters given by the direct arguments to {Rangeary.initialize}

@return [HashInf, Hash]

# File lib/rangeary/util/hash_inf.rb, line 96
def merge_hashinf(other, force: false)
  rethsinf = HashInf.construct(self)
  rethsinf.merge_hashinf!(other, force: force)
  rethsinf
end
merge_hashinf!(other, force: false) click to toggle source

Destructive version of {#merge_hashinf}

@param (see posinega_with_status=) @return [HashInf, Hash]

# File lib/rangeary/util/hash_inf.rb, line 106
def merge_hashinf!(other, force: false)
  if !other.respond_to? :status
    other = HashInf.construct(other)
  end

  POSNEG2METHOD.each_key do |posneg|
    cmped = (force ? 1 : (STATUS_PRIORITIES[self.status[posneg]] <=> STATUS_PRIORITIES[other.status[posneg]]))
    case cmped
    when -1
      # Do nothing
    when 0, 1
      next if false == other[posneg]  # no change if the status of "other" is false; n.b., when force==true, chances are self[posneg]!=false and other[posneg]==false, because "cmped" for the *case* is modified!
      if 0 == cmped
        if false != self[posneg]
          oper = ((posneg == :negative) ? :> : :<)
          to_change = _former_more_extreme?(oper, self[posneg], other[posneg])
          next if !to_change
        end
      end
      set_posinega_with_status(posneg, other[posneg], (force ? :definite : other.status[posneg]))
    else
      raise "should not happen. Contact the code developer."
    end
  end

  self
end
set_negative_with_status(infinity, stat) click to toggle source

Recommended way to set (update) the negative infinity with a status with verification

@param (see posinega_with_status=) @return [self]

# File lib/rangeary/util/hash_inf.rb, line 171
def set_negative_with_status(infinity, stat)
  set_posinega_with_status(:negative, infinity, stat)
end
set_posinega_with_status(posneg, infinity, stat) click to toggle source

Recommended way to set (update) the infinity in either polarity with a status with verification

@param infinity [Object] @param stat [Symbol, NilClass] one of {HashInf::STATUS_ORDERS}. @return [self]

# File lib/rangeary/util/hash_inf.rb, line 180
def set_posinega_with_status(posneg, infinity, stat)
  self[posneg] = infinity
  raise ArgumentError, "stat must be one of #{STATUS_ORDERS.inspect}" if !STATUS_ORDERS.include? stat
  self.status[posneg] = stat
  self
end
set_positive_with_status(infinity, stat) click to toggle source

Recommended way to set (update) the positive infinity with a status with verification

@param (see posinega_with_status=) @return [self]

# File lib/rangeary/util/hash_inf.rb, line 163
def set_positive_with_status(infinity, stat)
  set_posinega_with_status(:positive, infinity, stat)
end
status() click to toggle source

Reader and initializer of @status

status is a two-key (:negative and :positive) Hash with values of one of

  1. nil (initial condition)

  2. :guessed (if it is guessed, such as Float::INFINITY because Range contains an Integer.)

  3. :definite (if it is user-specified or exists in a Range, such as nil in +(5..nil)+.

# File lib/rangeary/util/hash_inf.rb, line 74
def status
  return @status if @status
  @status = {negative: nil, positive: nil}
end
status_is_a?(stat, posneg) click to toggle source

Return true if the specified infinity is with a specified status.

@param stat [Symbol, NilClass] One of the elements of {STATUS_ORDERS} @param posneg [Symbol] either :positive or :negative

# File lib/rangeary/util/hash_inf.rb, line 212
def status_is_a?(stat, posneg)
  raise ArgumentError, "Invalid specified status of #{stat.inspect}" if !STATUS_ORDERS.include? stat
  case posneg
  when :positive, :negative
    stat == status[posneg]
  else
    raise ArgumentError, "Invalid argument, neither :positive nor :negative: #{posneg.inspect}"
  end
end
status_is_nil?(posneg) click to toggle source

Return true if the specified infinity is nil.

@param posneg [Symbol] either :positive or :negative

# File lib/rangeary/util/hash_inf.rb, line 204
def status_is_nil?(posneg)
  status_is_a?(nil, posneg)
end

Private Instance Methods

_former_more_extreme?(oper, v1, v2) click to toggle source

Returns true if the former is more extreme.

If v1 is nil, returns true. If not and if v2 is nil, returns false.

The following fails because whereas the guessed positive infinity from the first one is Float:INFINITY, the second given infinity is RangeExtd::Infinity::POSITIVE, which is not comparable with Float:INFINITY (although it is comparable with general Float).

Rangeary(..6, 5..RangeExtd::Infinity::POSITIVE)

@param oper [Symbol] Method of operation: +return v1 if v1 oper v2+ @param v1 [Object] @param v2 [Object] @raise [ArgumentError]

# File lib/rangeary/util/hash_inf.rb, line 150
def _former_more_extreme?(oper, v1, v2)
  return true if v1.nil?
  return false if v2.nil?
  v1.send(oper, v2)
rescue ArgumentError => err
  raise ArgumentError, "Inconsistent given infinities: "+err.message
end