views:

94

answers:

5

I just wrote this piece of code but I'm not quite happy about it.

data = {}
options.each{ |k,v| data.merge!({k.to_s => v}) }

Basically I have:

{:a => "something", :b => "something else", :c => "blah"}

... and I want ...

{"a" => "something", "b" => "something else", "c" => "blah"}

... in order to send it to a gem that do not handle symbols for some reason. In the code I wrote options is the original hash and data is the updated one, but if I could only use 1 variable it'd be even better.

How would you guys refactor my code?

+6  A: 
data = Hash[options.map{ |k,v| [k.to_s,v] }]

For a hash large enough to be interesting, there isn't a significant difference between the answers

require 'benchmark'
options = Hash[('aaaa'..'zzzz').map{|i| [i.to_sym,i]}]
Benchmark.bm(100) do |x|
        x.report("map")   {Hash[options.map{ |k,v| [k.to_s,v] }] }
        x.report("zip")   {Hash[options.keys.map(&:to_s).zip(options.values)]}
        x.report("inject") {options.inject({}) { |h, (k, v)| h[k.to_s] = v; h }}
end

                          user     system      total        real
map                   3.490000   0.090000   3.580000 (  4.049015)
zip                   3.780000   0.020000   3.800000 (  3.925876)
inject                3.710000   0.110000   3.820000 (  4.289286)
gnibbler
that looks better, thanks! But creating a hash like that from an array, wouldn't that be slower than my solution? I know the difference would be minimal unless you would run it on a huge amount of data, but I'm still interested in knowing this
marcgg
I guess someone will have to try the various alternatives to see the relative performance.
gnibbler
It seems that inject is faster as @hellvinz pointed out
marcgg
@marcgg, the relative performance also depends on the size of the hash you are working with. Unless you know the hash will always be tiny it's usually better to choose the method that scales better when there is a significant difference. Otherwise choose whichever you think is more readable. Benchmarks can change significantly from one version or ruby to another, so what is faster today might not be next year.
gnibbler
@gnibbler: that sounds fair, thanks!
marcgg
+1  A: 

I'd suggest something like this:

hsh = data.inject({}) { |h, (k, v)| h[k.to_s] = v; h }

(Taken from a similar SO question)

Jeriko
+1  A: 

Rails adds a stringify_keys method to Hash. If you don't use Rails, you can simply copy the code from ActiveSupport (open source ftw!):

def stringify_keys
  inject({}) do |options, (key, value)|
    options[key.to_s] = value
    options
  end
end
Teoulas
thanks, I totally forgot about stringify_keys!
marcgg
+2  A: 

Inject seems to win:

require 'benchmark'
a = {:a => "something", :b => "something else", :c => "blah"}
Benchmark.bm(10000) do |x|
        x.report("map")   {Hash[a.map{ |k,v| [k.to_s,v] }] }
        x.report("zip")   {Hash[a.keys.map(&:to_s).zip(a.values)]}
        x.report("inject") {a.inject({}) { |h, (k, v)| h[k.to_s] = v; h }}
end

gives

user     system      total        real
map      0.000000   0.000000   0.000000 (  0.000033)
zip      0.000000   0.000000   0.000000 (  0.000018)
inject   0.000000   0.000000   0.000000 (  0.000014)
hellvinz
You should have benchmarked with bigger number of iterations, these timings look too close to zero to be relevant.
Mladen Jablanović
A: 

With the aid of the activesupport gem, you can make a new hash (or convert an existing one) wherein string and hash keys are interchangeable.

#!/usr/bin/ruby1.8

require 'active_support'

h = HashWithIndifferentAccess.new({'a'=>1, :b=>2})
p h[:a]     => 1
p h['a']    => 1
p h[:b]     => 2
p h['b']    => 2
Wayne Conrad