Here is my solution for Ruby Quiz 128. It's a little rough, but I  
can't afford to spend any more time working on it. One thing I didn't  
have time to do was to provide a user interface. Another thing I  
didn't do was proper commenting. I apologize for the latter.

I thought a Darwinian search (aka genetic algorithm) would be an  
interesting way to tackle this quiz. I have been looking for a excuse  
to write such a search in Ruby for quite awhile and this seemed to be  
it.

Here are is the output from one run of my quiz solution:

<result>
Solution found after 15 steps
SEND+MORE=MONEY
9567+1085=10652

Solution found after 27 steps
FORTY+TEN+TEN=SIXTY
29786+850+850=31486
</result>

And here is the code:

<code>
#! /usr/bin/env ruby -w
#
#  solution.rb
#  Quiz 128
#
#  Created by Morton Goldberg on 2007-06-18.

# Assumption: equations take the form: term + term + ... term = sum

ROOT_DIR = File.dirname(__FILE__)
$LOAD_PATH << File.join(ROOT_DIR, "lib")

require "cryptarithm"
require "solver"

EQUATION_1 = "SEND+MORE=MONEY"
EQUATION_2 = "FORTY+TEN+TEN=SIXTY"
POP_SIZE = 400
FECUNDITY = 2
STEPS = 50

Cryptarithm.equation(EQUATION_1)
s = Solver.new(POP_SIZE, FECUNDITY, STEPS)
s.run
puts s.show

Cryptarithm.equation(EQUATION_2)
s = Solver.new(POP_SIZE, FECUNDITY, STEPS)
s.run
puts s.show
</code>

And here are the library classes:

<code>
#  lib/cryptarithm.rb
#  Quiz 128
#
#  Created by Morton Goldberg on 2007-06-18.

DIGITS = (0..9).to_a

class Cryptarithm
    @@equation = ""
    @@max_rank = -1
    def self.equation(str=nil)
       if str
          @@equation = str.upcase
          lhs, rhs = @@equation.gsub(/[A-Z]/, "9").split("=")
          @@max_rank = [eval(lhs), eval(rhs)].max
       else
          @@equation
       end
    end
    attr_accessor :ranking, :solution
    def initialize
       @solution = @@equation.delete("+-=").split("").uniq
       @solution = @solution.zip((DIGITS.sort_by {rand})[0,  
@solution.size])
       rank
    end
    def mutate(where=rand(@solution.size))
       raise RangeError unless (0... / solution.size).include?(where)
       digits = @solution.collect { |pair| pair[1] }
       digits = DIGITS - digits
       return if digits.empty?
       @solution[where][1] = digits[rand(digits.size)]
    end
    def swap
       m = rand(@solution.size)
       n = m
       while n == m
          n = rand(@solution.size)
       end
       @solution[m][1], @solution[n][1] = @solution[n][1], @solution 
[m][1]
    end
    def rank
       sum = @@equation.dup
       solution.each { |chr, num| sum.gsub!(chr, num.to_s) }
       lhs, rhs = sum.split("=")
       terms = lhs.split("+") << rhs
       if terms.any? { |t| t[0] == ?0 }
          @ranking = @@max_rank
       else
          @ranking = eval("#{lhs} - #{rhs}").abs
       end
    end
    def initialize_copy(original)
       @solution = original.solution.collect { |pair| pair.dup }
    end
    def inspect
       [@ranking, @solution].inspect
    end
    def to_s
       sum = @@equation.dup
       solution.each { |chr, num| sum.gsub!(chr, num.to_s) }
       "#{@@equation}\n#{sum}"
    end
end
</code>

<code>
#  lib/solver.rb
#  Quiz 128
#
#  Created by Morton Goldberg on 2007-06-18.
#
# Attempts to a solve cryptarithm puzzle by applying a Darwinian search
# (aka genetic algorithm). It can thought of as a stochastic breadth- 
first
# search. Although this method doesn't guarantee a solution will be
# found, it often finds one quite quickly.

MUTATION = 0.5
SWAP = 1.0

class Solver
    attr_reader :best, :population, :step
    def initialize(pop_size, fecundity, steps)
       @pop_size = pop_size
       @fecundity = fecundity
       @steps = steps
       @mid_step = steps / 2
       @step = 1
       @population = []
       @pop_size.times { @population << Cryptarithm.new }
       select
    end
    def run
       @steps.times do
          replicate
          select
          break if @best.rank.zero?
          @step += 1
       end
       @best
    end
    def replicate
       @pop_size.times do |n|
          crypt = @population[n]
          # mate = crypt
          # while mate.equal?(crypt)
          #    mate = @population[rand(@pop_size)]
          # end
          @fecundity.times do
             child = crypt.dup
             child.mutate if crypt.solution.size < 10 && rand <=  
MUTATION
             child.swap if rand <= SWAP
             @population << child
          end
       end
    end
    def select
       @population = @population.sort_by { |crypt| crypt.rank }
       @population = @population[0, @pop_size]
       @best = @population.first
    end
    def show
       if @step > @steps
          "No solution found after #{step} steps"
       else
          "Solution found after #{step} steps\n" + @best.to_s
       end
    end
end
</code>

Regards, Morton