Code Sharing - Units (abandoned child)

(Gavin Kistner) #1

Last night I was sleepily trying to calculate the sustained transfer rate my web server would need to maintain to reach my quoted quota of 300GB/month transfter. It sparked an idea, and this morning I played around with some inferential unit conversion code. I don't have the energy to finish it off (it's more than just polish), but thought I'd share it anyhow. I like the syntax it allows :slight_smile:

Example code usage:

Units.add_conversion( 60.seconds, 1.minute )
Units.add_conversion( 1.mile_per_hour, 1.46666667.feet_per_second )
puts ( 32.feet_per_second_second * 1.5.minutes.in_seconds ).in_miles_per_hour
#=> 1963.63635917355 miles/hour

distance = 3.feet
time = 1.second
rate = distance / time
puts rate, rate / 3
#=> 3 feet/second
#=> 1 foot/second

puts 18.camels * 12.days / 4.cows + 89.widget_jobbers
#=> ((54 camel*day/cow) + (89 jobber*widget))

Things it doesn't do that it should, IMO:
1) More robust pluralization/singularization of nouns

2) Accept scalar factors/divisors.

3) Automatically search for a path between two conversions.
(For example, if it knows how to convert from GB/s to MB/s and from MB/s to kB/s, it should be smart enough to know how to find the path from GB/s to kB/s.)

4) Extend Numeric operators to turn the tables around if the right operand is a Quantity or Expression.

5) All sorts of fun symbolic math with Expressions

6) Use conversions to flatten Expressions with convertible-units.
(For example, (1.hour + 30.minutes) should be able to be automatically converted into a single Quantity using only hours or minutes.)

IMO it shouldn't know anything about any sort of units a priori, but instead require things like Unit.add_conversion( 1.mile_per_hour, 1.mph ).

module Units
聽聽聽def method_missing( name, *args )
聽聽聽聽聽top_units, bot_units = Units.from_string( name, self!=1 )
聽聽聽聽聽Quantity.new( self, top_units, bot_units )
聽聽聽end

聽聽聽def Units.add_conversion( q1, q2 )
聽聽聽聽聽@conversions ||= {}
聽聽聽聽聽(@conversions[ q1.units ] ||= {})[ q2.units ] = 1.0 * q2.value / q1.value
聽聽聽聽聽(@conversions[ q2.units ] ||= {})[ q1.units ] = 1.0 * q1.value / q2.value
聽聽聽end

聽聽聽def Units.convert( q1, units )
聽聽聽聽聽convert_to = ( @conversions ||= {} )[ q1.units ]
聽聽聽聽聽if convert_to && factor = convert_to[ units ]
聽聽聽聽聽聽聽Quantity.new( q1.value * factor, *units )
聽聽聽聽聽else
聽聽聽聽聽聽聽raise "I don't know how to convert from #{q1.units} to #{units}"
聽聽聽聽聽end
聽聽聽end

聽聽聽def Units.from_string( description, singularize=true )
聽聽聽聽聽top = []
聽聽聽聽聽bot = []
聽聽聽聽聽section = top
聽聽聽聽聽description.to_s.split( '_' ).each{ |unit|
聽聽聽聽聽聽聽case unit
聽聽聽聽聽聽聽聽聽when 'in'
聽聽聽聽聽聽聽聽聽聽聽raise "Cannot create 'in' units (reserved for conversion)"
聽聽聽聽聽聽聽聽聽when 'per'
聽聽聽聽聽聽聽聽聽聽聽section = bot
聽聽聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽聽聽section << ( singularize ? unit.singular : unit )
聽聽聽聽聽聽聽end
聽聽聽聽聽}
聽聽聽聽聽return [top, bot]
聽聽聽end

聽聽聽class Quantity
聽聽聽聽聽attr_reader :value, :units, :top_units, :bot_units

聽聽聽聽聽def initialize( value, top_units=[], bot_units=[] )
聽聽聽聽聽聽聽@value = value
聽聽聽聽聽聽聽@top_units = [].concat( top_units )
聽聽聽聽聽聽聽@bot_units = [].concat( bot_units )
聽聽聽聽聽聽聽@units = [ @top_units, @bot_units ]

聽聽聽聽聽聽聽#Simplify
聽聽聽聽聽聽聽removed = []
聽聽聽聽聽聽聽@bot_units.each_with_index{ |divisor, div_i|
聽聽聽聽聽聽聽聽聽if i = @top_units.index( divisor )
聽聽聽聽聽聽聽聽聽聽聽@top_units.delete_at( i )
聽聽聽聽聽聽聽聽聽聽聽removed << div_i
聽聽聽聽聽聽聽聽聽end
聽聽聽聽聽聽聽}
聽聽聽聽聽聽聽unless removed.empty?
聽聽聽聽聽聽聽聽聽removed.each{ |divisor_index|
聽聽聽聽聽聽聽聽聽聽聽@bot_units.delete_at( divisor_index )
聽聽聽聽聽聽聽聽聽}
聽聽聽聽聽聽聽end

聽聽聽聽聽聽聽@top_units.sort!
聽聽聽聽聽聽聽@bot_units.sort!
聽聽聽聽聽end

聽聽聽聽聽def dup
聽聽聽聽聽聽聽self.class.new( @value, @top_units, @bot_units )
聽聽聽聽聽end

聽聽聽聽聽def method_missing( name, *args )
聽聽聽聽聽聽聽if ( name = name.to_s ) =~ /^in_/
聽聽聽聽聽聽聽聽聽Units.convert( self, Units.from_string( name.sub( /^in_/, '' ) ) )
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽top, bot = Units.from_string( name )
聽聽聽聽聽聽聽聽聽self.class.new( @value, name, @top_units + top, @bot_units + bot )
聽聽聽聽聽聽聽end
聽聽聽聽聽end

聽聽聽聽聽def same_units_as?( other )
聽聽聽聽聽聽聽return false unless other.respond_to? :units
聽聽聽聽聽聽聽self.units == other.units
聽聽聽聽聽end

聽聽聽聽聽def combine_units( *quantities )
聽聽聽聽聽聽聽quantities.each{ |q|
聽聽聽聽聽聽聽聽聽@top_units.concat( q.top_units )
聽聽聽聽聽聽聽聽聽@bot_units.concat( q.bot_units )
聽聽聽聽聽聽聽}
聽聽聽聽聽end

聽聽聽聽聽def +( other )
聽聽聽聽聽聽聽if self.same_units_as?( other )
聽聽聽聽聽聽聽聽聽self.class.new( @value + other.value, @top_units, @bot_units )
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽Expression.new( self, :+, other )
聽聽聽聽聽聽聽end
聽聽聽聽聽end

聽聽聽聽聽def -( other )
聽聽聽聽聽聽聽if self.same_units_as?( other )
聽聽聽聽聽聽聽聽聽self.class.new( @value - other.value, @top_units, @bot_units )
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽Expression.new( self, :-, other )
聽聽聽聽聽聽聽end
聽聽聽聽聽end

聽聽聽聽聽def *( other )
聽聽聽聽聽聽聽if other.respond_to? :units
聽聽聽聽聽聽聽聽聽self.class.new( @value * other.value, @top_units + other.top_units, @bot_units + other.bot_units )
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽self.class.new( @value * other, @top_units, @bot_units )
聽聽聽聽聽聽聽end
聽聽聽聽聽end

聽聽聽聽聽def /( other )
聽聽聽聽聽聽聽if other.respond_to? :units
聽聽聽聽聽聽聽聽聽self.class.new( @value / other.value, @top_units + other.bot_units, @bot_units + other.top_units )
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽self.class.new( @value / other, @top_units, @bot_units )
聽聽聽聽聽聽聽end
聽聽聽聽聽end

聽聽聽聽聽def to_s
聽聽聽聽聽聽聽wrap = @top_units.length > 1 or @bot_units.length > 0
聽聽聽聽聽聽聽out = wrap ? '(' : ''
聽聽聽聽聽聽聽out << "#@value "
聽聽聽聽聽聽聽if @value != 1 && @top_units.length == 1
聽聽聽聽聽聽聽聽聽out << @top_units.first.plural
聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽out << @top_units.join( '*' )
聽聽聽聽聽聽聽end
聽聽聽聽聽聽聽unless @bot_units.empty?
聽聽聽聽聽聽聽聽聽out << '/'
聽聽聽聽聽聽聽聽聽out << @bot_units.join( '*' )
聽聽聽聽聽聽聽end
聽聽聽聽聽聽聽out << ')' if wrap
聽聽聽聽聽聽聽out
聽聽聽聽聽end
聽聽聽end

聽聽聽class Expression
聽聽聽聽聽attr_reader :o1, :op, :o2
聽聽聽聽聽def initialize( o1, op, o2 )
聽聽聽聽聽聽聽@o1 = o1
聽聽聽聽聽聽聽@op = op
聽聽聽聽聽聽聽@o2 = o2
聽聽聽聽聽end

聽聽聽聽聽def to_s
聽聽聽聽聽聽聽"(#@o1 #@op #@o2)"
聽聽聽聽聽end
聽聽聽end

end

class String
聽聽聽def singular
聽聽聽聽聽self.gsub( /(([hs])e)?s$/, '\2' ).gsub( 'feet', 'foot' )
聽聽聽end
聽聽聽def plural
聽聽聽聽聽out = self + ( ( self =~ /[hs]$/ ) ? 'es' : 's' )
聽聽聽聽聽out.gsub( 'foots', 'feet' )
聽聽聽end
end

class Numeric
聽聽聽include Units
end

(Ara.T.Howard) #2

you might be interested in this:

聽聽聽http://raa.ruby-lang.org/project/quanty/

you're code looks very interesting too though...

cheers.

-a

路路路

On Thu, 1 Sep 2005, Gavin Kistner wrote:

Last night I was sleepily trying to calculate the sustained transfer rate my web server would need to maintain to reach my quoted quota of 300GB/month transfter. It sparked an idea, and this morning I played around with some inferential unit conversion code. I don't have the energy to finish it off (it's more than just polish), but thought I'd share it anyhow. I like the syntax it allows :slight_smile:

Example code usage:

Units.add_conversion( 60.seconds, 1.minute )
Units.add_conversion( 1.mile_per_hour, 1.46666667.feet_per_second )
puts ( 32.feet_per_second_second * 1.5.minutes.in_seconds ).in_miles_per_hour
#=> 1963.63635917355 miles/hour

distance = 3.feet
time = 1.second
rate = distance / time
puts rate, rate / 3
#=> 3 feet/second
#=> 1 foot/second

puts 18.camels * 12.days / 4.cows + 89.widget_jobbers
#=> ((54 camel*day/cow) + (89 jobber*widget))

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
Your life dwells amoung the causes of death
Like a lamp standing in a strong breeze. --Nagarjuna

===============================================================================

(Jeff Wood) #3

Gavin Kistner wrote:

Last night I was sleepily trying to calculate the sustained transfer rate my web server would need to maintain to reach my quoted quota of 300GB/month transfter. It sparked an idea, and this morning I played around with some inferential unit conversion code. I don't have the energy to finish it off (it's more than just polish), but thought I'd share it anyhow. I like the syntax it allows :slight_smile:

Example code usage:

Units.add_conversion( 60.seconds, 1.minute )
Units.add_conversion( 1.mile_per_hour, 1.46666667.feet_per_second )
puts ( 32.feet_per_second_second * 1.5.minutes.in_seconds ).in_miles_per_hour
#=> 1963.63635917355 miles/hour

distance = 3.feet
time = 1.second
rate = distance / time
puts rate, rate / 3
#=> 3 feet/second
#=> 1 foot/second

puts 18.camels * 12.days / 4.cows + 89.widget_jobbers
#=> ((54 camel*day/cow) + (89 jobber*widget))

Things it doesn't do that it should, IMO:
1) More robust pluralization/singularization of nouns

2) Accept scalar factors/divisors.

3) Automatically search for a path between two conversions.
(For example, if it knows how to convert from GB/s to MB/s and from MB/s to kB/s, it should be smart enough to know how to find the path from GB/s to kB/s.)

4) Extend Numeric operators to turn the tables around if the right operand is a Quantity or Expression.

5) All sorts of fun symbolic math with Expressions

6) Use conversions to flatten Expressions with convertible-units.
(For example, (1.hour + 30.minutes) should be able to be automatically converted into a single Quantity using only hours or minutes.)

IMO it shouldn't know anything about any sort of units a priori, but instead require things like Unit.add_conversion( 1.mile_per_hour, 1.mph ).

module Units
聽聽def method_missing( name, *args )
聽聽聽聽top_units, bot_units = Units.from_string( name, self!=1 )
聽聽聽聽Quantity.new( self, top_units, bot_units )
聽聽end

聽聽def Units.add_conversion( q1, q2 )
聽聽聽聽@conversions ||= {}
聽聽聽聽(@conversions[ q1.units ] ||= {})[ q2.units ] = 1.0 * q2.value / q1.value
聽聽聽聽(@conversions[ q2.units ] ||= {})[ q1.units ] = 1.0 * q1.value / q2.value
聽聽end

聽聽def Units.convert( q1, units )
聽聽聽聽convert_to = ( @conversions ||= {} )[ q1.units ]
聽聽聽聽if convert_to && factor = convert_to[ units ]
聽聽聽聽聽聽Quantity.new( q1.value * factor, *units )
聽聽聽聽else
聽聽聽聽聽聽raise "I don't know how to convert from #{q1.units} to #{units}"
聽聽聽聽end
聽聽end

聽聽def Units.from_string( description, singularize=true )
聽聽聽聽top = []
聽聽聽聽bot = []
聽聽聽聽section = top
聽聽聽聽description.to_s.split( '_' ).each{ |unit|
聽聽聽聽聽聽case unit
聽聽聽聽聽聽聽聽when 'in'
聽聽聽聽聽聽聽聽聽聽raise "Cannot create 'in' units (reserved for conversion)"
聽聽聽聽聽聽聽聽when 'per'
聽聽聽聽聽聽聽聽聽聽section = bot
聽聽聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽聽聽section << ( singularize ? unit.singular : unit )
聽聽聽聽聽聽end
聽聽聽聽}
聽聽聽聽return [top, bot]
聽聽end

聽聽class Quantity
聽聽聽聽attr_reader :value, :units, :top_units, :bot_units

聽聽聽聽def initialize( value, top_units=[], bot_units=[] )
聽聽聽聽聽聽@value = value
聽聽聽聽聽聽@top_units = [].concat( top_units )
聽聽聽聽聽聽@bot_units = [].concat( bot_units )
聽聽聽聽聽聽@units = [ @top_units, @bot_units ]

聽聽聽聽聽聽#Simplify
聽聽聽聽聽聽removed = []
聽聽聽聽聽聽@bot_units.each_with_index{ |divisor, div_i|
聽聽聽聽聽聽聽聽if i = @top_units.index( divisor )
聽聽聽聽聽聽聽聽聽聽@top_units.delete_at( i )
聽聽聽聽聽聽聽聽聽聽removed << div_i
聽聽聽聽聽聽聽聽end
聽聽聽聽聽聽}
聽聽聽聽聽聽unless removed.empty?
聽聽聽聽聽聽聽聽removed.each{ |divisor_index|
聽聽聽聽聽聽聽聽聽聽@bot_units.delete_at( divisor_index )
聽聽聽聽聽聽聽聽}
聽聽聽聽聽聽end

聽聽聽聽聽聽@top_units.sort!
聽聽聽聽聽聽@bot_units.sort!
聽聽聽聽end

聽聽聽聽def dup
聽聽聽聽聽聽self.class.new( @value, @top_units, @bot_units )
聽聽聽聽end

聽聽聽聽def method_missing( name, *args )
聽聽聽聽聽聽if ( name = name.to_s ) =~ /^in_/
聽聽聽聽聽聽聽聽Units.convert( self, Units.from_string( name.sub( /^in_/, '' ) ) )
聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽top, bot = Units.from_string( name )
聽聽聽聽聽聽聽聽self.class.new( @value, name, @top_units + top, @bot_units + bot )
聽聽聽聽聽聽end
聽聽聽聽end

聽聽聽聽def same_units_as?( other )
聽聽聽聽聽聽return false unless other.respond_to? :units
聽聽聽聽聽聽self.units == other.units
聽聽聽聽end

聽聽聽聽def combine_units( *quantities )
聽聽聽聽聽聽quantities.each{ |q|
聽聽聽聽聽聽聽聽@top_units.concat( q.top_units )
聽聽聽聽聽聽聽聽@bot_units.concat( q.bot_units )
聽聽聽聽聽聽}
聽聽聽聽end

聽聽聽聽def +( other )
聽聽聽聽聽聽if self.same_units_as?( other )
聽聽聽聽聽聽聽聽self.class.new( @value + other.value, @top_units, @bot_units )
聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽Expression.new( self, :+, other )
聽聽聽聽聽聽end
聽聽聽聽end

聽聽聽聽def -( other )
聽聽聽聽聽聽if self.same_units_as?( other )
聽聽聽聽聽聽聽聽self.class.new( @value - other.value, @top_units, @bot_units )
聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽Expression.new( self, :-, other )
聽聽聽聽聽聽end
聽聽聽聽end

聽聽聽聽def *( other )
聽聽聽聽聽聽if other.respond_to? :units
聽聽聽聽聽聽聽聽self.class.new( @value * other.value, @top_units + other.top_units, @bot_units + other.bot_units )
聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽self.class.new( @value * other, @top_units, @bot_units )
聽聽聽聽聽聽end
聽聽聽聽end

聽聽聽聽def /( other )
聽聽聽聽聽聽if other.respond_to? :units
聽聽聽聽聽聽聽聽self.class.new( @value / other.value, @top_units + other.bot_units, @bot_units + other.top_units )
聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽self.class.new( @value / other, @top_units, @bot_units )
聽聽聽聽聽聽end
聽聽聽聽end

聽聽聽聽def to_s
聽聽聽聽聽聽wrap = @top_units.length > 1 or @bot_units.length > 0
聽聽聽聽聽聽out = wrap ? '(' : ''
聽聽聽聽聽聽out << "#@value "
聽聽聽聽聽聽if @value != 1 && @top_units.length == 1
聽聽聽聽聽聽聽聽out << @top_units.first.plural
聽聽聽聽聽聽else
聽聽聽聽聽聽聽聽out << @top_units.join( '*' )
聽聽聽聽聽聽end
聽聽聽聽聽聽unless @bot_units.empty?
聽聽聽聽聽聽聽聽out << '/'
聽聽聽聽聽聽聽聽out << @bot_units.join( '*' )
聽聽聽聽聽聽end
聽聽聽聽聽聽out << ')' if wrap
聽聽聽聽聽聽out
聽聽聽聽end
聽聽end

聽聽class Expression
聽聽聽聽attr_reader :o1, :op, :o2
聽聽聽聽def initialize( o1, op, o2 )
聽聽聽聽聽聽@o1 = o1
聽聽聽聽聽聽@op = op
聽聽聽聽聽聽@o2 = o2
聽聽聽聽end

聽聽聽聽def to_s
聽聽聽聽聽聽"(#@o1 #@op #@o2)"
聽聽聽聽end
聽聽end

end

class String
聽聽def singular
聽聽聽聽self.gsub( /(([hs])e)?s$/, '\2' ).gsub( 'feet', 'foot' )
聽聽end
聽聽def plural
聽聽聽聽out = self + ( ( self =~ /[hs]$/ ) ? 'es' : 's' )
聽聽聽聽out.gsub( 'foots', 'feet' )
聽聽end
end

class Numeric
聽聽include Units
end

... You might want to look at the Mega modules ( http://mega.rubyforge.org ) ... Then you can see the similar functionality they've already built, and maybe where you can fit your stuff in.

j.

(Adam Sanderson) #4

Damnit, ruby is too much fun. I was just thinking I was going to
need to do some simple unit conversions, and behold.

聽聽I have seen some other modules that do this, but your code looks very
natural to use. For the singular and plural issue, check out the way
they do it in Rails, it works pretty well. I've used it a few times.

I might play with this a little today.
聽聽.adam sanderson

(Gavin Kistner) #5

Er, I must have missed it...where is there a module in that collection that does anything like this?

路路路

On Aug 31, 2005, at 11:14 PM, Jeff Wood wrote:

Gavin Kistner wrote:

Units.add_conversion( 60.seconds, 1.minute )
Units.add_conversion( 1.mile_per_hour, 1.46666667.feet_per_second )
puts ( 32.feet_per_second_second * 1.5.minutes.in_seconds ).in_miles_per_hour
#=> 1963.63635917355 miles/hour

... You might want to look at the Mega modules ( http://mega.rubyforge.org ) ... Then you can see the similar functionality they've already built, and maybe where you can fit your stuff in.