Saturday, August 03, 2013

Ruby - Find Phone Numbers corresponding to Words

Remember the 1800 numbers a part of which match up with a name like "1-800 walmart".

I wanted to write a program to convert a name into the corresponding number. I decided to try this first with Ruby. So, here is version 1:

#!/usr/bin/env ruby

print "Enter the name: "
name = gets.chomp
downname=name.downcase

number = downname.gsub(/[abc]/,"2").gsub(/[def]/,"3")
                 .gsub(/[ghi]/,"4").gsub(/[jkl]/,"5")
                 .gsub(/[mno]/,"6").gsub(/[pqrs]/,"7")
                 .gsub(/[tuv]/,"8").gsub(/[wxyz]/,"9")
                 .gsub(/ /,"0") 

puts "The Number corresponding #{name} is #{number}"

The output for this is as follows:
$ ./name_2_number.rb 
Enter the name: Karthick
The Number corresponding Karthick is 52784425
$ ./name_2_number.rb 
Enter the name: 1800-walmart
The Number corresponding 1800-walmart is 1800-9256278

To explain the program:
  1. Line number 1 is the sha-bang. I will put in a separate post to explain that one.
  2. Line number 3 prints the message "Enter the name: ".
  3. Line number 4 accepts the input using gets and removes the \n at the end of the accepted by using chomp. Though not essential in this case, it is generally a good practice to do a chomp of the inputs obtained from the user.
  4. Line number 5 converts the name into lower case character. For this purpose, I am using the downcase method of the String in ruby. Changing the text into lower case helps in simplifying the regular expression (regex, for short) in the next line.
  5. Line number 7 is the one that contains the core logic. In this line, I use the gsub method in String class to replace occurrences of each of the letters with the corresponding numbers. Note that the first argument of gsub is a regex, while the second argument is a string. The sequence of gsub calls replace all occurrences of alphabets and spaces with the corresponding numbers.
  6. Line number 13 prints the message containing the original name given and the corresponding number. I have used puts over here because I want a newline to be appended to the end of the message. This is the difference between a print and puts in ruby. Also, I had created the variable downname so that I can use name in this display.
As you can see, this program works fine but there are some basic issues with this script:

  1. Line number 7 is not efficient. Multiple calls to gsub is the culprit. 
  2. Line number 7 is long and unwieldy.
  3. The maintainer of this code must understand the regex. Based on what I have seen, a surprising number of software engineers are not good with regex.

So, here is version 2:
#!/usr/bin/env ruby

print "Enter the name: "
name = gets.chomp
downname=name.downcase

repl = {'a' => '2', 'b' => '2', 'c' => '2',
        'd' => '3', 'e' => '3', 'f' => '3',
        'g' => '4', 'h' => '4', 'i' => '4',
        'j' => '5', 'k' => '5', 'l' => '5',
        'm' => '6', 'n' => '6', 'o' => '6',
        'p' => '7', 'q' => '7', 'r' => '7', 's' => '7',
        't' => '8', 'u' => '8', 'v' => '8',
        'w' => '9', 'x' => '9', 'y' => '9', 'z' => '9',
        ' ' => '0'}

number = downname.gsub(/[a-z ]/) { |m| repl[m] }

puts "The Number corresponding #{name} is #{number}"

Two lines have changed from the original script. Let me explain these two lines alone:
  1. Line number 7 declares a hash of the mapping between the each letter and its corresponding number. Note that this includes a blank space as one of the characters and it is mapped to 0.
  2. Line number 9 calls one gsub and does the replacement of the values by using the hash declared in the line number 7.
This version works fine in ruby 1.8 and 1.9. However, ruby 1.9 has a shortcut for line number 9 in version 2. Here is the modified script (version 3):
#!/usr/bin/env ruby

# Works with ruby version greater than 1.9

print "Enter the name: "
name = gets.chomp
downname=name.downcase

repl = {'a' => '2', 'b' => '2', 'c' => '2',
        'd' => '3', 'e' => '3', 'f' => '3',
        'g' => '4', 'h' => '4', 'i' => '4',
        'j' => '5', 'k' => '5', 'l' => '5',
        'm' => '6', 'n' => '6', 'o' => '6',
        'p' => '7', 'q' => '7', 'r' => '7', 's' => '7',
        't' => '8', 'u' => '8', 'v' => '8',
        'w' => '9', 'x' => '9', 'y' => '9', 'z' => '9',
        ' ' => '0'}

number = downname.gsub(/[a-z]/, repl)

puts "The Number corresponding #{name} is #{number}"
The new version of gsub does the replacement. It does this by taking the regular expression as the first argument and the second argument as the hash.

Cheers!
Karthick S.
Post a Comment