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.
精彩评论