(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'

  it "assigns strings beginning with 'F' but not 'Foo' to 'group A'" do
    expect(subject.lookup('Fbar')).to eq 'group B'

  it "assigns strings beginning with 'Foo' to 'group A'" do
    expect(subject.lookup('Bar')).to eq 'group C'

  it "strings beginning with 'Baz' have 'a's changed to '0's" do
    expect(subject.lookup('Bazaz')).to eq 'B0z0z'

and the code:

# string_lookup.rb
module RegexAndLambdaHash
  def value_for_key_matching(string)
    pattern = self.keys.find { |key| key =~ string }

    value_for(pattern, string)

  def value_for(pattern_key, original_key)
    value = self[pattern_key]

    if value.respond_to? :call

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)

Let’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_matching can’t handle string keys
  • RegexAndLambdaHash will have to be maintained
  • for Ruby versions older than 1.9 LOOKUP will have to be an ActiveSupport::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