Hashes vs. case statements, Part 1
(or, sometimes fun isn’t necessarily best)
A few weeks back I needed to write some code that would take a string as input and produce an output string according to rules similar to the following:
- Strings beginning with ‘Foo’ => ‘group A’
- Strings beginning with ‘F’ (but not ‘Foo’) => ‘group B’
- Strings beginning with ‘Bar’ => ‘group C’
- Strings beginning with ‘Baz’ => same string, but replace all ‘a’s with ‘0’s
The ruleset was small, and the number of input strings was not particularly large, maybe in the tens to low hundreds.
Implementing this with a Hash, regex, and lambdas sounded like fun, so I did that.
Here are the specs:
# string_lookup_spec.rb
require 'string_lookup'
describe StringLookup do
subject { StringLookup }
it "assigns strings beginning with 'Foo' to 'group A'" do
expect(subject.lookup('Foobar')).to eq 'group A'
end
it "assigns strings beginning with 'F' but not 'Foo' to 'group A'" do
expect(subject.lookup('Fbar')).to eq 'group B'
end
it "assigns strings beginning with 'Foo' to 'group A'" do
expect(subject.lookup('Bar')).to eq 'group C'
end
it "strings beginning with 'Baz' have 'a's changed to '0's" do
expect(subject.lookup('Bazaz')).to eq 'B0z0z'
end
endand the code:
# string_lookup.rb
module RegexAndLambdaHash
def value_for_key_matching(string)
pattern = self.keys.find { |key| key =~ string }
value_for(pattern, string)
end
def value_for(pattern_key, original_key)
value = self[pattern_key]
if value.respond_to? :call
value.call(original_key)
else
value
end
end
end
module StringLookup
LOOKUP = {
/^Foo.*$/ => 'group A',
/^F.*$/ => 'group B',
/^Bar.*$/ => 'group C',
/^Baz.*$/ => lambda { |x| x.gsub('a','0') },
}.extend RegexAndLambdaHash
def self.lookup(string)
LOOKUP.value_for_key_matching(string)
end
endLet’s see what this does. LOOKUP is defined with regexes as keys and the desired strings as values. RegexAndLambdaHash#value_for_key_matching allows strings to be matched to the regex keys. #value_for returns the corresponding hash value or calls the lambda.
This code was fun to write, but there are some problems.
#value_for_key_matchingcan’t handle string keysRegexAndLambdaHashwill have to be maintained- for Ruby versions older than 1.9
LOOKUPwill have to be anActiveSupport::OrderedHash. This is because prior to 1.9 hashes were unordered
In the next post let’s see if a case statement works better.
She generally gave herself very good advice, (though she very seldom followed it)
– Through the Looking-Glass by Lewis Carroll