Skip to content

Instantly share code, notes, and snippets.

@kotp
Last active December 11, 2019 01:24
Show Gist options
  • Save kotp/7628cf7a4745c9179c66 to your computer and use it in GitHub Desktop.
Save kotp/7628cf7a4745c9179c66 to your computer and use it in GitHub Desktop.
Build #Hash with #Derivative Value
=begin
Method returns hash of derivitave values as provided.
Example usage:
doctest: Setup
>> require './build_hash_with_derived_values'
=> true
doctest: Yardstick test derived result
>> yardstick = build_hash_with_derivative_values [:yard, :foot, :inch],
[ 1, 3, 12 ]
=> {:inch=>36, :foot=>3, :yard=>1}
doctest: How many inches in 1.5 feet?
We can remember how to do this if we remember that inches in something means
to divide that something by inches. Then 1.5 of that something (where "of"
means to multiply).
>> yardstick[:inch] / yardstick[:foot] * 1.5
=> 18.0
doctest: Units that make up a yard
>> yardstick.keys
=> [:yard, :foot, :inch]
doctest: Units in Year derived result
>> Units_In_One_Year =
build_hash_with_derivative_values [:year, :day, :hour, :minute, :second],
[ 1, 365, 24, 60, 60]
=> {:second=>31536000, :minute=>525600, :hour=>8760, :day=>365, :year=>1}
=end
def build_hash_with_derivative_values units, property
decrement = 0 ; value = nil # house keeping for the hash assignment
hash = {}
hash = units.inject do |key|
(units.size - decrement ).times do |i|
value = 1 unless value # This is opposite of the x ||= value idiom
value = property[i] * value
hash[units[decrement]] = value
decrement += 1 # control the map of the supplied value array.
end
value = nil
hash # this is returned internally to inject.
end
# hash as created by the inect method is returned.
end
@citizen428
Copy link

citizen428 commented Jul 2, 2010

def build_hash_with_derivative_values(units, property)
  properties = (property.inject([1]) { |a,v| a << v*a[-1] })[1..-1] # This returns the elements at this range
  units.zip(properties).inject({}) { |h, (k,v)| h[k] = v ; h }
end

build_hash_with_derivative_values([:yard, :foot, :inch], [1, 3, 12]) 
# => {:yard=>1, :foot=>3, :inch=>36}
build_hash_with_derivative_values([:year, :day, :hour, :minute, :second], [1, 365, 24, 60, 60]) 
# => {:year=>1, :day=>365, :hour=>8760, :minute=>525600, :second=>31536000}
# This one works!! confirmed by vhgiii

@citizen428
Copy link

citizen428 commented Jul 2, 2010

Just a slight variation of the above, that saves you from always having to pass in 1 as the first value of property:

# This one fails the test, output below in comments doesn't match the doctest
def build_hash_with_derivative_values(units, property)
  properties = property.inject([1]) { |a,v| a << v*a[-1] }
  units.zip(properties).inject({}) { |h, (k,v)| h[k] = v ; h }
end

build_hash_with_derivative_values([:yard, :foot, :inch], [3, 12]) # this complicates the API... IMHO
# => {:yard=>1, :foot=>3, :inch=>36}
build_hash_with_derivative_values([:year, :day, :hour, :minute, :second], [365, 24, 60, 60]) 
# => {:year=>1, :day=>365, :hour=>8760, :minute=>525600, :second=>31536000}

Confirmed Doctest Output
#

Thinking about it, maybe :yard and :year shouldn't be part of the hash at all:

def build_hash_with_derivative_values(units, property)
  properties = property.inject([1]) { |a,v| a << v*a[-1] }
  units.zip(properties[1..-1]).inject({}) { |h, (k,v)| h[k] = v ; h }
end

yard = build_hash_with_derivative_values([:foot, :inch], [3, 12]) 
# => {:foot=>3, :inch=>36}
year = build_hash_with_derivative_values([:day, :hour, :minute, :second], [365, 24, 60, 60]) 
# => {:day=>365, :hour=>8760, :minute=>525600, :second=>31536000}

yard[:inch]   # => 36
year[:second] # => 31536000

@kotp
Copy link
Author

kotp commented Jul 2, 2010

Yeah, I thought about the not having the base be included. I am still kind of at a loss as to whether that is a good idea.

My thought is on the possibility of expanding the set. For example:

time_units = build_hash_with_derivative_values([:day, :hour, :minute, :second], [365, 24, 60, 60])
and then expand time units to include :millenium, :century, :decade, :year

@citizen428
Copy link

I see :-) Do you like the solution though?

@kotp
Copy link
Author

kotp commented Jul 3, 2010

Oh, do I like it? No... I love that solution!

It is definitely readable, (as soon as you get a handle on what inject is doing)

@ashbb
Copy link

ashbb commented Jul 3, 2010

Wow, interesting idea, Vic. Awesome solution, Michael.
I was inspired. How about this one? Just for fun, though. :)

class Fixnum
  def self.build_hash_with_derivative_values(units, property)
    properties = (property.inject([1]) { |a,v| a << v*a[-1] })[1..-1]
    units.zip(properties).inject({}) { |h, (k,v)| h[k] = v ; h }
  end

  @@h = build_hash_with_derivative_values [:year, :day, :hour, :minute, :second], [1, 365, 24, 60, 60]

  def method_missing m
    from, to = m.to_s.split('_to_')
    1.0 * self * @@h[to.to_sym] / @@h[from.to_sym] 
  end
end

puts 3.year_to_day      #=> 1095.0
puts 3.hour_to_second   #=> 10800.0
puts 10.minute_to_hour  #=> 0.166666666666667

@kotp
Copy link
Author

kotp commented Jul 3, 2010

That is pretty cool...

@kotp
Copy link
Author

kotp commented Jul 3, 2010

Jerry Anning had realized that my original code above (specifically version https://gist.github.com/7628cf7a4745c9179c66/4e40a35942c60d97bdf3fb1259ad7a82fd31dab8 ) passes the test if I comment the inject line, and the appropriate 'end'... which means that I basically built something that does both lines:
properties = property.inject([1]) { |a,v| a << v*a[-1] }
units.zip(properties).inject({}) { |h, (k,v)| h[k] = v ; h }

@citizen428
Copy link

Victor: I didn't think your original solution was bad, it's readable and does what it's supposed to do. My solution was more a for fun approach and because you asked me on IRC re inject. Given that my main interest nowadays is functional programming, I tend to use the likes of inject, map, zip, etc. a lot. I even have a project here on GH that's supposed to one day become an ebook on functional programming in Ruby, alas I never have enough time for all my different projects...

@kotp
Copy link
Author

kotp commented Jul 3, 2010

Thank you... The reason I asked for inject, is because it is way over due that I actually understand how inject works, past the trivial (though mystical) (1..5).inject(:+) invocation, or the written out (1..5).inject { |sum, n| sum + n }

I thank you tons for your solution, because it gave me something more complex to look at, while realizing that my code did the same thing, and gave me more area to understand in the novelty of that small space, and something to compare the behavior and activity with.

I really didn't take it as an opinion on my original solution. I just knew that it 'looked' like it was doing something that could be done differently.

I actually have about 4 different solutions, all that pass the doctests and it will be interesting to see which is more efficient in terms of benchmarks, and profiler measurements. I wish I programmed every day... actually with people, because it is times like these that I learn the most. I only have certain viewpoints, and I stretch them, but it helps to have other eyes on the matter too.

It is why I like working with you guys.

@citizen428
Copy link

Funny that you bring up (1..5).inject(&:+), since it always struck me as a particularly bad example, since summing the numbers from 1 to n is most efficiently done as (n*(n+1))/2 (http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/runsums/triNbProof.html). A couple of days ago I actually benchmarked it because I was thinking about a blog post called "Why knowing your math helps" and the second solution was considerably faster than inject.

BTW: the written out form of your inject would just return 5, what you meant is (1..5).inject { |sum, n| sum + n }

@kotp
Copy link
Author

kotp commented Dec 11, 2019

Does math always win out over inject/iterations? I can't think of a situation where it wouldn't, but then I am not a math nerd.

Also, happy holidays to wherever both are!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment