1
\$\begingroup\$

is there a better why how can I refactor this code, making sure that values in a Hash are typecasted to true/false if their value is '1' or '0' while leaving unaltered the rest?

I'm using Ruby 2.0.0 if that matters, and I'd like to improve this code.

  def transform
    hsh = {}
    preferences.each do |k, v|
      v = case v
      when '1'
        true
      when '0'
        false
      else
        v
      end
      hsh[k.to_sym] = v
    end
    hsh
  end

updated with benchmarks

All right, here's the performance test based on the replies so far:

class Test
  HSH = {"xxx"=>"xxx-rrr", "yyy"=>"0", "rrr"=>"1", "nnn"=>"0", "kkk"=>"1", "iii"=>"1", "lll"=>"default", "mmm"=>"76", "www"=>"1"}

  def self.transform_case
    hsh = {}
    HSH.each do |k, v|
      v = case v
      when '1'
        true
      when '0'
        false
      else
        v
      end
      hsh[k.to_sym] = v
    end
    hsh
  end

  def self.transform_ternary
    Hash[ HSH.map { |k, v| [k.to_sym, v == '1' ? true : v == '2' ? false : v ] } ]
  end

  def self.transform_fetch
    special_values = {"1" => true, "0" => false}
    Hash[HSH.map { |k, v| [k.to_sym, special_values.fetch(v, v)] }]
  end

  def self.transform_negation
    Hash[ preferences.map {|k,v| [k.to_sym, !!v]} ]
  end
end

Benchmark.bm(20) do|b|

  b.report('case') do
    1500.times { Test.transform_case }
  end

  b.report('ternary') do
    1500.times { Test.transform_ternary }
  end

  b.report('fetch') do
    1500.times { Test.transform_fetch }
  end

  b.report('negation') do
    1500.times { Test.transform_negation }
  end
end

                           user     system      total        real
case                   0.010000   0.000000   0.010000 (  0.009282)
ternary                0.010000   0.000000   0.010000 (  0.013794)
fetch                  0.010000   0.000000   0.010000 (  0.012804)
negation               0.010000   0.000000   0.010000 (  0.010760)

It appears that my original implementation is faster. Or is the BM test wrong?

\$\endgroup\$
3
  • \$\begingroup\$ don't select an answer so soon! leave time for others to answer. \$\endgroup\$
    – tokland
    Commented Jun 3, 2013 at 14:27
  • \$\begingroup\$ Oh ok, thanks :) I'm going to do some benchmarks between the replies before selecting then. Thanks! \$\endgroup\$
    – John Smith
    Commented Jun 3, 2013 at 14:38
  • 1
    \$\begingroup\$ I agree with @tokland that this kind of benchmark is not quite relevant : as your current code works, the question you asked was implicitly about expressiveness. And then : 1) your HSH should be a lot longer for your benchmark to be representative (possible differences in big'O) 2) in transform_fetch you create a special_values hash every time the method is called, which is not necessary and hurts performance \$\endgroup\$
    – m_x
    Commented Jun 4, 2013 at 7:25

2 Answers 2

4
\$\begingroup\$

Code should be as declarative as possible (usually by using functional style):

def transform
  special_values = {"1" => true, "0" => false}
  Hash[preferences.map { |k, v| [k.to_sym, special_values.fetch(v, v)] }]
end

However, Hash[...] is very ugly and I prefer a more OOP approach with Enumerable#mash, so I'd really write preferences.mash { |k, v| [k.to_sym, special_values.fetch(v, v)] }.

\$\endgroup\$
2
  • \$\begingroup\$ Added benchmarks to the original post \$\endgroup\$
    – John Smith
    Commented Jun 3, 2013 at 14:45
  • 3
    \$\begingroup\$ @John: A benchmark of this kind of code is not relevant. Go for the most declarative code. \$\endgroup\$
    – tokland
    Commented Jun 3, 2013 at 15:17
0
\$\begingroup\$

You can do this:

Hash[ preferences.map {|k,v| [k.to_sym, v.nonzero? or false]} ]

This preserves the value if it is nonzero, otherwise makes it false. It does not change a 1 to true though. I like @tokland's answer best, because it not only does the exact transformation you want, but makes it easy to extend and use as a general purpose transformer (if you ever need to change other values as well), which one might assume is your purpose for this method, based on the name.

Code review

Note that when you initialize a return variable and then loop over another variable, e.g.

hsh = {}
preferences.each do
  #something that updates hsh
end

A more idiomatic Ruby way to do this is with a map, as the answers show. This will in general result in more concise, expressive code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.