开发者

What's the best way of define transformation rules in ruby?

Some times you need to "map" a set of values against another set of values, so when you get a value from the first set, you return the corresponding value of the other. It's like the concept of mathematical function: x -> f(x).

So, I found it can be achieved with two methods: you can define the "transformation rule" as a hash:

rule = {
  'A' => 'Excelent',
  'B' => 'Great',
  'C' => 'Good'
}

and apply it to a given input like this:

a = rule[input_1]
b = rule[input_2]

or you can use a case block, wrapped by a function:

开发者_StackOverflow社区
def rule(input)
  case input
    when 'A'; 'Excelent'
    when 'B'; 'Great'
    when 'C'; 'Good'
  end
end

and then apply the rule like this:

a = rule(input_1)
b = rule(input_2)

So, I'd like to know the pros and cons of each one and if there's a "more standard" solution for what, I think, it's a very common problem. (These 2 just don't "feel" ideal).


I'm not going to try and compare the approaches in any detail, but as far as I'm concerned a Hash is designed to be an explicit mapping just like a discrete function, so that's where I'd always start from.

Case statements do have a lot of great uses in their more flexible forms, but they never feel like the right tool for me for this


I ♥ hashes. I even mention this exact use case in one of my blog post.

If your hash is constructed once (say as a constant within a module), the lookup will be faster than the equivalent case. I feel a hash makes it clearer at first glance that you are mapping a value to another while a case has to be inspected in full to be sure there isn't anything else happening.

What's more, don't forget about Hash#default_proc which enables you to handle more complex cases. E.g.:

rule = {
  'A' => 'Excellent',
  'B' => 'Great',
  'C' => 'Good'
}
rule.default_proc = ->(h, k){
  new_k = k.to_s.strip.upcase
  h[new_k] unless k == new_k
}

rule['B']   # => 'Great'
rule[:a]    # => 'Excellent'
rule['      c       '] # => 'Good'
rule['foo'] # => nil

The first lookup will be immediate and won't execute the default_proc, while the other two will succeed after executing it.

Note: I'm assuming Ruby 1.9, must be adapted in Ruby 1.8 by using Hash.new with a block + merge or require "backports" and use lambda instead of ->...


You should consider a Proc or a lambda. :)

rule = Proc.new{|x| # implement rule logic here }

Now you can pass the rule object around just like a variable. When you need to you invoke it by

a = rule.call(input1)
b = rule.call(input2)

If you lookup rule is simple lookup hash, stick to your version 1 above. If your "rule" is complex, and requires more complex programming, consider delving deeper into Ruby's Proc/lambda functionality.


I'd stick with the Hash solution because of the maintainability and efficiency. With a Hash, you have O(1) time to access the value from a given key, whereas in the second example the Ruby interpretor will have to do to === comparisons which is not as desirable in terms of efficiency.

Benchmark:

#hash_test.rb
rule = {
  'A' => 'Excelent',
  'B' => 'Great',
  'C' => 'Good'
}

1_000_000.times{
  rule['A']
  rule['B']
  rule['C']
}


time ruby hash_test.rb 
real    0m0.880s
user    0m0.869s
sys  0m0.007s




 #case_test.rb
 def rule(input)
  case input
    when 'A'; 'Excelent'
    when 'B'; 'Great'
    when 'C'; 'Good'
  end
end


1_000_000.times{
rule('A')
rule('B')
rule('C')
}


time ruby case_test.rb 

real    0m1.486s
user    0m1.474s
sys 0m0.008s


When you use the case method, ruby must evaluate, from top to bottom each individual case and compare 'string' values. With a Hash you ruby will compare 'hashes' of strings (integers!) which is much faster. However, the actual implementation may vary on platform and ruby environment. My intuition is that the hash will never be slower than case, but case will almost always be slower than a hash.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜