class Measured::ConversionTableBuilder
Attributes
units[R]
Public Class Methods
new(units, cache: nil)
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 5 def initialize(units, cache: nil) @units = units cache ||= { class: Measured::Cache::Null } @cache = cache[:class].new(*cache[:args]) end
Public Instance Methods
cached?()
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 20 def cached? @cache.exist? end
to_h()
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 11 def to_h return @cache.read if cached? generate_table end
update_cache()
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 16 def update_cache @cache.write(generate_table) end
Private Instance Methods
find_conversion(to:, from:)
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 59 def find_conversion(to:, from:) conversion = find_direct_conversion_cached(to: to, from: from) || find_tree_traversal_conversion(to: to, from: from) raise Measured::MissingConversionPath.new(from, to) unless conversion conversion end
find_direct_conversion(to:, from:)
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 78 def find_direct_conversion(to:, from:) units.each do |unit| return unit.conversion_amount if unit.name == from && unit.conversion_unit == to return unit.inverse_conversion_amount if unit.name == to && unit.conversion_unit == from end nil end
find_direct_conversion_cached(to:, from:)
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 67 def find_direct_conversion_cached(to:, from:) @direct_conversion_cache ||= {} @direct_conversion_cache[to] ||= {} if @direct_conversion_cache[to].key?(from) @direct_conversion_cache[to][from] else @direct_conversion_cache[to][from] = find_direct_conversion(to: to, from: from) end end
find_tree_traversal_conversion(to:, from:)
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 87 def find_tree_traversal_conversion(to:, from:) traverse(from: from, to: to, units_remaining: units.map(&:name), amount: 1) end
generate_table()
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 26 def generate_table validate_no_cycles units.map(&:name).each_with_object({}) do |to_unit, table| to_table = {to_unit => Rational(1, 1)} table.each do |from_unit, from_table| conversion = find_conversion(to: from_unit, from: to_unit) to_table[from_unit] = conversion from_table[to_unit] = 1 / conversion end table[to_unit] = to_table end end
traverse(from:, to:, units_remaining:, amount:)
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 91 def traverse(from:, to:, units_remaining:, amount:) units_remaining = units_remaining - [from] units_remaining.each do |name| conversion = find_direct_conversion_cached(from: from, to: name) if conversion new_amount = amount * conversion if name == to return new_amount else result = traverse(from: name, to: to, units_remaining: units_remaining, amount: new_amount) return result if result end end end nil end
validate_acyclic_graph(graph, from:, visited: [])
click to toggle source
This uses a depth-first search algorithm: en.wikipedia.org/wiki/Depth-first_search
# File lib/measured/conversion_table_builder.rb, line 48 def validate_acyclic_graph(graph, from:, visited: []) graph[from]&.each do |edge| adjacent_node = edge.conversion_unit if visited.include?(adjacent_node) raise Measured::CycleDetected.new(edge) else validate_acyclic_graph(graph, from: adjacent_node, visited: visited + [adjacent_node]) end end end
validate_no_cycles()
click to toggle source
# File lib/measured/conversion_table_builder.rb, line 42 def validate_no_cycles graph = units.select { |unit| unit.conversion_unit.present? }.group_by { |unit| unit.name } validate_acyclic_graph(graph, from: graph.keys[0]) end