Archive for October, 2008

Word Cheat, a simple anagram engine

Those of you who follow this blog since its inception will be aware of my obsession with word games, and specifically algorithms that solve word games.

Yesterday I was playing Word Challenge on Facebook, a simple game where you have to find all the words that can be composed with six random letters. After a couple of trials I decided to try and solve it with a ruby program, but since I didn’t want to use my previous code I decided to try a new approach to solve this problem.

First of all I took an extensive list of Italian words and filtered it to suit my needs, leaving only the words ranging from three to six letters.

Once I had my wordlist I had to build a data structure that could be used for fast anagrams retrieval, thus the WordHash class was born.

class WordHash
  attr_reader :words
 
  def initialize(wordlist)
    @words = {}
    wordlist.each do |word|
      if @words.has_key?(w = word.strip.signature)
        @words[w] << word.strip
      else
        @words[w] = [word.strip]
      end
    end
  end
 
  def get_anagrams(word)
    result = []
    wordlist = @words.each do |k,w|
      result << w if word.contains?(k)
    end
    result
  end
end

This class makes use of two helper methods I added to String, that I think should be really part of the Ruby standard library:

module StringExtensions
  def signature
    self.split('').sort.join
  end
 
  def contains?(search)
   !Regexp.new(search.signature.split("").join(".*")).match(self.signature).nil?
  end
end
 
String.class_eval do
  include StringExtensions
end
The way it all works is quite simple, I build a Hash whose keys are the different signatures of words in the wordlist and whose values are arrays made up by the words with the same signature, so fetching all the anagrams for a word means just accessing word_hash[word.signature].

The get_anagrams method is used to also get anagrams with a length less than that of the original word.

The whole project, complete with tests and a helper script is available on GitHub, feel free to contribute.

Also, big thanks to Stefano Cobianchi, who contributed with the contains? method, that I really couldn’t code :)

Radiant iPhone Extension 0.0.1

Fellow Mikamai – er, all around nice guy, great guitarist and top notch developer Andrea “Pilu” Franz has just released an incredibile extension for the Radiant CMS, the iPhone Extension, it allows you to access your radiant admin interface via iPhone using a nice iPhone optimized GUI. Please check the original article on his blog.

Two improvements to your Capfiles

I have lately started using a pattern that has become quite common among capistrano users: setting the server names and locations in a task. Doing this allows you to have multiple deployment environments, like development, staging, production, and so on.

desc "deploy to development environment"
task :development do
  set :deploy_to, "/var/apps/#{application}"
 
  role :web, "servername.mikamai.com", :primary => true
  role :app, "servername.mikamai.com", :primary => true
  role :db, "servername.mikamai.com", :primary => true
 
  set :user, "username"
  set :password, "secr3t"
  set :remote_mysqldump, "/usr/bin/mysqldump"
 
  set :db_user, "username"
  set :db_password, "secre7"
  set :db_name, "db_name"
end

This technique has proven itself to be really useful, especially when clients start to ask for deployments on their test servers, and you still want to be able to deploy to your development servers.

While refactoring my Capfiles I also took the time to rewrite the drupal:db namespace, adding the much needed tasks that allow you dump the remote databases and download them to your development box.

  namespace :db do    
    namespace :dump do
      desc "Deletes old database dumps, leaves only the latest on the server"
      task :cleanup, :roles => :db do
        dumps = capture("ls -xt #{shared_path}/dumps").split.reverse
        run "cd #{shared_path}/dumps; rm #{dumps[0..-2].join(" ")}"
      end
 
      desc "Dumps the local database"
      task :local, :roles => :db do
        raise RuntimeError.new("failed dump") unless system "#{local_mysqldump} -u #{local_db_user} --password=#{local_db_password} #{local_db_name} > dump.sql"
      end
 
      namespace :remote do
        desc "Dumps the remote database"
        task :default, :roles => :db do
          filename = "#{Time.now.to_i.to_s}.dump.sql"
          run "cd #{shared_path}/dumps; #{remote_mysqldump} -u #{db_user} --password=#{db_password} #{db_name} > #{filename}"
          run "cd #{shared_path}/dumps; bzip2 #{filename}"
        end          
 
        namespace :download do
          desc "Dumps and downloads the remote database"
          task :default do
            drupal::db::dump::remote::default
            latest
          end
 
          desc "Downloads the latest database dump"
          task :latest, :roles => :db do
            dumps = capture("ls -xt #{shared_path}/dumps").split.reverse
            get("#{shared_path}/dumps/#{dumps.last}", "./#{dumps.last}")
          end
 
        end
 
      end
 
    end