class UnitSoup::Soup

Attributes

name[R]

Public Class Methods

new(name="_") click to toggle source
# File lib/unit_soup/soup.rb, line 10
def initialize(name="_")
  @name = name
  @mix = Mix.new("default")
  @units = Set.new
  @rules = Set.new
  # symbol -> unit
  @symbols_map = {}
  @lookup = {}
  @graph = {}
end

Public Instance Methods

<<(m) click to toggle source
# File lib/unit_soup/soup.rb, line 21
def <<(m)
  @mix << m
end
convert(value, from, to) click to toggle source
# File lib/unit_soup/soup.rb, line 55
def convert(value, from, to)
  from = Unit.new(from)
  to = Unit.new(to)
  return nil unless @units.include?(from) && @units.include?(to)
  return (value * (@lookup[[from, to]]).amount) if @lookup.include? [from, to]
  # bfs graph from from-to. When found, walk through parent path and multiply all conversion factors.
  Struct.new("Child", :measurement, :parent)
  queue = [Struct::Child.new(Measurement.new(value, from), nil)]
  while !queue.empty? do
    parent = queue.first
    # Find all measurement matches
    # e.g. if graph[:from] = ["3/4 :foo", "4/5 :bar", "3/1 :to", "5/1 :to"]
    #    then matches = ["3/1 :to", "5/1 :to"]
    #    Since there are two valid rules to convert :from -> :to, we pick the first one
    matches = @graph[parent.measurement.unit].select{|m|m.unit == to}
    if(matches.size > 0)
      # create the chain [from, a, b, c, to], starting with 'to' and building back
      chain_from_to = [Struct::Child.new(matches.first, parent)]
      curr = chain_from_to.first
      while(curr.parent) do
        chain_from_to.unshift curr.parent # add current parent to front of chain
        curr = curr.parent
      end
      return chain_from_to.map{|c|c.measurement.amount}.inject(1){|m1,m2| m1 * m2}
    else
      children = @graph[parent.measurement.unit]
      children_not_in_queue = children.reject{|m| queue.include? m}
      queue += children_not_in_queue.map{|m| Struct::Child.new(m, parent)}
      queue.shift
    end
  end
end
make() click to toggle source
# File lib/unit_soup/soup.rb, line 29
def make
  @units.clear
  @lookup = {}
  @graph = {}
  @mix.rules.each do |r|
    @units << r.this_measurement.unit
    @units << r.that_measurement.unit
    this_unit = r.this_measurement.unit
    that_unit = r.that_measurement.unit

    this_in_that = Measurement.new((r.that_measurement.amount/r.this_measurement.amount).rationalize, that_unit)
    that_in_this = Measurement.new((r.this_measurement.amount/r.that_measurement.amount).rationalize, this_unit)

    # add direct conversions to lookup
    @lookup[[r.this_measurement.unit, r.that_measurement.unit]] = this_in_that
    @lookup[[r.that_measurement.unit, r.this_measurement.unit]] = that_in_this

    # build graph
    # rule "3 foo = 4 bar" represented as {foo => [4/3 bar], bar => [3/4 foo]}
    @graph[r.this_measurement.unit] = Set.new unless @graph.include?(r.this_measurement.unit)
    @graph[r.this_measurement.unit] << this_in_that
    @graph[r.that_measurement.unit] = Set.new unless @graph.include?(r.that_measurement.unit)
    @graph[r.that_measurement.unit] << that_in_this
  end
end
rules() click to toggle source
# File lib/unit_soup/soup.rb, line 25
def rules
  @mix.rules
end