Martin Larsson
Martin Larsson

Reputation: 1068

Variable assignment using hash vs if statements

Lately when coding in Ruby, when I need a variable assigned differently depending on some input to a method, I've been using a Hash like this:

variable = { "option1" => [1,2,3], "option2" => [3,2,1]}[input]

The thing I like most about this approach is the scalability: the input can be one of, say, five different values, which with if statements would be a bit messy.

But what am I losing when doing this? Larger memory use as the Hash needs to be initialized? Slower? Faster?

For comparison, an equivalent if statement could be:

variable = Array.new
if input.eql?("option1")
  variable = [1,2,3]
else
  variable = [3,2,1]
end

(Yes, I could have used ? notation for two options, but not for more, so I wanted to show the structure in comparison.)

Upvotes: 1

Views: 601

Answers (3)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230346

One of reasons I prefer hash approach is that it shifts logic to the data itself. Now if you add more rules, you just have to change data, not the code. And you can load data from external source (config file, network, etc.). In short: flexibility.

If we are talking about performance, then these two approaches are pretty much identical. I doubt that you'll be able to detect any differences (in a real world app).

Upvotes: 3

undur_gongor
undur_gongor

Reputation: 15954

If you are using the hash, be sure to define it once (as a constant).

Some benchmarking (for runtime):

#!/usr/local/bin/ruby -w

require 'benchmark'

def by_hash1(i)
  { "option1" => [1,2,3], "option2" => [3,2,1] }[i]
end

TheHash = { 
  "option1" => [1,2,3], 
  "option2" => [3,2,1],
}
def by_hash2(i)
  TheHash[i]
end

def by_case(i)
  case i
  when 'option1'
    [1, 2, 3]
  when 'option2'
    [3, 2, 1]
  end
end

def by_if(i)
  if i.equal?('option1')
    [1, 2, 3]
  else
    [3, 2, 1]
  end
end

class Foo
  def self.option1
    [1, 2, 3]
  end

  def self.option2
    [3, 4, 5]
  end
end


N = 10_000_000
Inps = %w{ option1 option2 }

Benchmark.bm(10) do | x |
  x.report('by hash1') { N.times { by_hash1(Inps.sample) } }
  x.report('by hash2') { N.times { by_hash2(Inps.sample) } }
  x.report('by case')  { N.times { by_case(Inps.sample)  } }
  x.report('by if')    { N.times { by_if(Inps.sample)  } }
  x.report('meta')     { N.times { Foo.send(Inps.sample) } }
end

gives

                 user     system      total        real
by hash1    11.529000   0.000000  11.529000 ( 11.597000)
by hash2     2.387000   0.000000   2.387000 (  2.401000)
by case      3.151000   0.000000   3.151000 (  3.155000)
by if        3.198000   0.000000   3.198000 (  3.236000)
meta         3.541000   0.000000   3.541000 (  3.554000)

on my ruby 2.0.0p195 (2013-05-14) [x64-mingw32].

Please note that I think performance is normally of minor importance. Only if you encounter performance problems you should start investigating. Otherwise, things like readability are more important.

Upvotes: 3

Pierre-Louis Gottfrois
Pierre-Louis Gottfrois

Reputation: 17631

I see two more options:

Using case

case input
when 'option1'
  [1, 2, 3]
else
   # default here or error message 
end

Using metaprogramming:

class Foo
  def option1
    [1, 2, 3]
  end

  def option2
    [3, 4, 5]
  end
end

Foo.new.send(input)

A benchmark would help us determine the performance of all the solutions given. However, performance is great but code smell, readability and reusability is something your need to be aware of too. Using a Hash is more flexible than a if else statement but you may found yourself reaching the limit of the hash implementation at some point.

case are more readable but can be quite ugly when having lots of conditions...

Metaprogramming is great and gives you lots of flexibility but might be overkill in your case.

Upvotes: 6

Related Questions