···
#
# Units data file for Ruby Quiz #183
#
# -----------------------------------
#
1 in = 0.0254 m
1 l = 0.001 m3
use SI prefixes for m g l m3
_____________________________________________
# file: units.rb
module Conversion
#
# A primitive parser to create the conversion data structure, syntax
checks are
# omitted to shorten the solution ;).
#
class LineParser
SIUnits = { "Y" => 10**24, "Z" => 10**21, "E" => 10**18, "P" =>
10**15, "T" => 10**12,
"G" => 10**9, "M" => 1_000_000, "k" => 1000, "h" => 100, "D" => 10,
"d" => 0.1, "c" => 0.01, "m" => 0.001, "µ" => 0.000_001, "n" =>
10**-9, "p" => 10**-12,
"f" => 10**-15, "a" => 10**-18, "z"=> 10**-21, "y" => 10**-24 }
def parse_line line
return if /^\s*#/ === line || line.strip.empty?
line = line.strip.sub /#.*/, ""
case line
when /^use SI prefixes for /
add_si_units line.split[4..-1]
else
add_conversion *line.split
end
end
def traverse &blk
@c.each do | unit, conversion_hash |
_traverse unit, conversion_hash, [ unit ], &blk
end
end
private
def add_conversion lhs_value, lhs_unit, equal_dummy, rhs_value, rhs_unit
@c[ lhs_unit ][ rhs_unit ] = Float( rhs_value ) / Float( lhs_value )
@c[ rhs_unit ][ lhs_unit ] = Float( lhs_value ) / Float( rhs_value )
end
def add_si_units units
units.each do |unit|
add_si_unit_for unit
end
end
def add_si_unit_for unit
SIUnits.each do | prefix, conversion |
@c[ prefix + unit ][ unit ] = conversion
@c[ unit ][ prefix + unit ] = 1 / conversion
end
end
def initialize
@c = Hash::new{ | h, k | h[ k ] = { } }
end
def _traverse src_unit, unit_conversions, traversed_units, f=1.0, &blk
unit_conversions.each do | new_unit, conversion |
next if traversed_units.include? new_unit
blk.call src_unit, new_unit, f * conversion
_traverse src_unit, @c[ new_unit ], traversed_units + [ new_unit ], f
* conversion, &blk
end
end
end
#
# Will be subclassed for each unit.
# Each such sublcass will than get all conversion methods defined dynamically
# by means of exploring "units.txt"
#
class ProxyClass
# syntactic sugar
def to; self end
alias_method :as, :to
private
def initialize value
@value = value
end
end
ProxyClasses = {} # maps units to their proxy classes
def method_missing unit_name
pc = ProxyClasses[ unit_name.to_s ] || super( unit_name )
pc::new self
end
conversions = LineParser::new
File::open "units.txt" do | f |
f.each do | line |
conversions.parse_line line
end
end
conversions.traverse do | src_unit, tgt_unit, conversion |
( ProxyClasses[ src_unit ] ||= Class::new ProxyClass ).module_eval do
define_method tgt_unit do (@value * conversion).to_s + tgt_unit end
end
end
end
class Integer
include Conversion
end
class Float
include Conversion
end
_________________________________________________________________
# file: test-solution.rb
require 'units'
require 'test/unit'
class Test183_01 < Test::Unit::TestCase
def test_001_simple
assert_equal "25.4mm", 1.0.in.to.mm
assert_equal "1.0mm", 0.001.m.to.mm
assert_equal "0.0393700787401575in", 1.mm.in
assert_equal "39370.0787401575in", 1.km.as.in
assert_equal "0.001m3", 1.0.l.as.m3
end
end
Thx for the quiz.
--
Ne baisse jamais la tête, tu ne verrais plus les étoiles.
Robert Dober 