Thursday, April 19, 2007

Ruby and the old phone programming challenge

I recently came across an old programming language challenge while I was looking for the new D programming language (http://www.digitalmars.com/d/lisp-java-d.html). The challenge is to write a program which will output all possible combinations of phone numbers with words (like 555-ruby) given a dictionnary and a phone number in input. Lionello Lunesu took 1h15 and 55 lines of D code to achieve the goal. With the links on his site, I then look at this site and this one from Peter Norvig, a Lisp programmer, which took 2h and 45 lines of lisp code to do it. Being a Ruby fan, I tried to solve this challenge with one of my favorite language: Ruby. I was telling everybody how much of a productivity boost I got since I learned this new language, I thought it was time to prove it. And guess what, I was able to achieve it with pretty good results: 20 lines of Ruby code in 50 minutes (not couting blanks and comments like everybody else)! Here is my code:

# Number/letters mapping
map = {'2'=>'a'..'c', '3'=>'d'..'f', '4'=>'g'..'i', '5'=>'j'..'l', '6'=>'m'..'o',
       '7'=>'p'..'s', '8'=>'t'..'v', '9'=>'w'..'z', '0'=>[], '1'=>[]}

if ARGV.length != 2
    puts 'Usage: {dictFile} {phoneNumber}'
else
    # Reading the file in memory (*nix or Windows (or Mac???))
    f = IO.popen("cat #{ARGV[0]}") rescue IO.popen("type #{ARGV[0]}") rescue fail '?'
    content = f.readlines

    # For each word found
    content.each do |word|
        word.chomp!.downcase!
        next if word.empty? or word.size != ARGV[1].size # If the word is not good.

        good_word = true
        ARGV[1].size.times do |i|

            # Comparing each letters
            if not map[ARGV[1][i,1]].include?(word[i,1])
                good_word = false
                break
            end
        end

        # Write it if it's good.
        puts word if good_word
    end
end

That's it! I took a dictionnary from OpenOffice to test my code and it seems to work fine. I know that I could optimize my code, but that's not the point of the challenge. The same thing can be achieved with different languages (Python for instance), but I wanted to share my solution for those of you who are looking at Ruby.