1. Technology
Send to a Friend via Email
You can opt-out at any time. Please refer to our privacy policy for contact information.

Calculating Abbreviations

By

Calculating Abbreviations

Many command line tools allow you to refer to commands in an abbreviated form. For example, running rails s is the same as running rails server. However, defining shortened forms of all of your commands can be a laborious process.

Ruby provides an Abbrev library as part of its standard library. No gems need to be installed, you can use it right out of the box. It will give you a list of abbreviations for any number of words (passed as an array) to the Abbrev.abbrev method.

So to start off, make sure you require 'abbrev'.


require "abbrev"

 > Abbrev.abbrev(['test'])
 => {"tes"=>"test", "te"=>"test", "t"=>"test", "test"=>"test"} 

What is returned is a hash. The keys are the abbreviated strings, the values are the original strings. Why this is useful will be apparent once we see it in action with more than one input string.

Unique Abbreviations

The naive way of solving this problem is to simply take substrings of each input string and forming the hash. However, if you have the input strings of "test" and "teleport", both t and te will be abbreviations for both input strings. Which one goes to which? The Abbrev library will only produce a hash containing unique abbreviations, leaving out abbreviations that are ambiguous. This means for some sets of strings it won't produce any abbreviations at all, but it's guaranteed not to produce any abbreviations that could possibly match more than one input string.

So let's try again with some more interesting input. First, we'll try with our ["test","teleport"] test case.


require 'abbrev'
require 'test/unit'

class AbbrevUniqueTester < Test::Unit::TestCase
  def test_unique
    abbrevs = Abbrev.abbrev(["test","teleport"])
    assert( !abbrevs.has_key?('t') )
    assert( !abbrevs.has_key?('te') )
    assert_equal( abbrevs['tes'], 'test' )
    assert_equal( abbrevs['tel'], 'teleport' )
  end
end

As you can see from this test case, Abbrev will not produce non-unique abbreviations. The shortest abbreviations in this case are "tes" and "tel".

Controlling Abbreviations

Sometimes you don't want just any abbreviations to exist. You could manually go through each key of the abbreviations hash and remove keys that you don't need. Or you could pass a second argument to Abbrev.abbrev. This second argument is a regular expression that, if it doesn't match the abbreviation, will prevent the abbreviation from begin added to the hash. So, say you don't want any abbreviations over four characters long. If the abbreviation is longer than that, you might as well just type the whole word, right? Or maybe you only want abbreviations that start with the letter 'q'? Your imagination (and needs) are the only limits here.

So let's try another test, but this time use a regular expression to limit what exactly can be an abbreviation.


class AbbrevRegexpTester < Test::Unit::TestCase
  def test_regexp
    abbrevs = Abbrev.abbrev(["server","console","debug"], /^.{0,3}$/)
    assert_equal( abbrevs.keys.select{|s| s.length > 3}.length, 0 )
  end
end

This rather long-winded (or at least long-lined) test case does two things. First, the regular expression only allows strings that are from 0 to 3 characters in length. Second, the assertion selects all strings from the set of keys that are longer than three lines and counts the length of the resulting array. If that array is zero elements long (as in, none of them were found to be longer than three characters), then the assertion will pass.

Using Abbrev

In this example, we'll make a small REPL (a "read, eval, print" loop, an easy to implement construct that allows interactive interactions with an object, much like IRB) that passes commands to an object. The commands can be abbreviated and the REPL will take this into account.


require 'abbrev'

class Counter
  def initialize
    @count = 0
  end

  def command_increment
    @count += 1
  end

  def command_decrement
    @count -= 1
  end

  def command_display
    puts @count
  end

  def do_command(command)
    send( ("command_" + command).to_sym )
  end

  def self.abbrevs
    Abbrev.abbrev(
      instance_methods.
        grep(/^command_/).
        map(&:to_s).
        map{|s| s.sub(/^command_/, '') }
    )
  end
end

count = Counter.new
loop do
  input = gets.chomp

  begin
    command = Counter.abbrevs[input]
    if command
      count.do_command( command )
    else
      puts "Unknown command"
    end
  rescue NoMethodError
    puts "Unknown command"
  end
end

The main things at work here are the Counter.abbrevs class method and the REPL itself. The class method does a bit of massaging on the data in order to get a list of command names. It will first grep the list of instance methods for method names beginning in command_, turn them all into strings (they were symbols before), and then remove the command_ prefixes. It feeds the output of this into the familiar Abbrev.abbrev method. Later on, the REPL will consult this hash before sending a command to the counter.

  1. About.com
  2. Technology
  3. Ruby
  4. Advanced Ruby
  5. Calculating Abbreviations

©2014 About.com. All rights reserved.