Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I am trying to make the fastest #to_struct method in Ruby's Hash.

I am including a use case and benchmark so you can run and see if you have really improved the code.

This is my implementation and the benchmark is included. The time at the bottom is the time it takes on my machine. How can I make this faster?

require "json"
require 'benchmark'
require 'bigdecimal/math'

class Hash
  def to_struct
    k = self.keys
    klass = k.map(&:to_s).sort_by {|word| word.downcase}.join.capitalize
    begin
      Kernel.const_get("Struct::" + klass).new(*self.values_at(*k))
    rescue NameError
      Struct.new(klass, *(k)).new(*self.values_at(*k))
    end
  end
end

# You have a hash that you have built in your app
sample_hash = {
  foo_key: "foo_val",
  bar_key: "bar_val",
  baz_key: "baz_val",
  foo1_key: "foo_val",
  bar1_key: "bar_val",
  baz1_key: "baz_val",
  foo2_key: "foo_val",
  bar2_key: "bar_val",
  baz2_key: "baz_val",
  foo3_key: "foo_val",
  bar3_key: "bar_val",
  baz3_key: "baz_val",
  foo4_key: "foo_val",
  bar4_key: "bar_val",
  baz4_key: "baz_val",
  foo5_key: "foo_val",
  bar5_key: "bar_val",
  baz5_key: "baz_val",
  foo6_key: "foo_val",
  bar6_key: "bar_val",
  baz6_key: "baz_val",
  foo7_key: "foo_val",
  bar7_key: "bar_val",
  baz7_key: "baz_val",
}

# Then you have JSON coming from some external api
json_response = "{\"qux_key\":\"qux_val\",\"quux_key\":\"quux_val\",\"corge_key\":\"corge_val\"}"
hash_with_unknown_keys = JSON.parse(json_response)

# Merge these two together
sample_hash.merge!(hash_with_unknown_keys)

iterations = 100_000

Benchmark.bm do |bm|
  bm.report "#to_struct" do
    iterations.times do
      # Would be super nice if I could convert this to a struct with a method
      # Somehow a bit faster than the explicit example below and much faster than open struct
      sample_struct = sample_hash.to_struct
      unless sample_struct.foo_key == "foo_val"
        raise "Wrong value"
      end
    end
  end

  bm.report "Struct" do
    iterations.times do
      sample_struct = Struct.new(*sample_hash.keys)
        .new(*sample_hash.values)
      unless sample_struct.foo_key == "foo_val"
        raise "Wrong value"
      end
    end
  end

  bm.report "OpenStruct" do
    iterations.times do
      sample_open_struct = OpenStruct.new(sample_hash)
      unless sample_open_struct.foo_key  == "foo_val"
        raise "Wrong value"
      end
    end
  end
end

#       user     system      total        real
# #to_struct  4.030000   0.010000   4.040000 (  4.072031)
#     Struct  6.870000   0.290000   7.160000 (  7.320459)
# OpenStruct 23.550000   0.210000  23.760000 ( 23.895187) 
share|improve this question

migrated from codegolf.stackexchange.com May 23 at 13:10

This question came from our site for programming puzzle enthusiasts and code golfers.

    
The benchmark is testing the time it takes to run #to_struct together with the time it takes to access the structure's attributes. Is that correct? –  Wayne Conrad May 24 at 22:44
    
@WayneConrad The benchmark has three different ways to instantiate similar ruby objects. The first two are two different ways to create structs and the third instantiates an open struct. And yes all three benchmarks access the objects attributes. –  mpiccolo May 27 at 3:14
    
I think the reason that the to_struct version is faster than the plain Struct version is that it's not doing the full task on each iteration. It only constructs a new Struct subclass on the first iteration, and then re-uses it on all the subsequent iterations. It's therefore not a strictly fair comparison, as the Struct method has to create a new class for each iteration. I think you'd find that if you randomised the hash keys for each iteration, there would be little difference between the to_struct version and the Struct version. –  AlexT yesterday
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.