Verbal Arithmetic (#128)

class VerbalArithmetic
   def initialize(expr)
      parse(expr)
      analys
   end
   attr_reader :factor, :sum, :table

   private

   def parse(expr)
      term = expr.scan(/\w+/)
      unless expr.count('=') == 1 && (expr.count('+') + 2) == term.length
         raise AugmentError, "syntax error: #{expr}"
      end
      @factors = term[0,(expr.count('+') + 1)]
      @sum = term.last
   end

   def make_chars
      @chars =[]
      (@factors + [@sum]).each do |f|
         f.scan(/./).each do |c|
            @chars << c unless @chars.include?(c)
         end
      end
      raise AugmentError, "too many character: #{@expr}" unless @chars.length =< 10
   end

   def analys
      make_chars
      backtrack([],(0..9).to_a)
   end

   def backtrack(table,rest)
      return calc(table) if table.length == @chars.length
      r = rest.each do |i|
         break if backtrack((table + [i]), (rest - [i]))
      end
      r == nil
   end
   def calc(t)
      first_is_zero = (@factors + [@sum]).inject(false) do|r,s|
         r or (t[@chars.index(s.slice(/./))] == 0)
      end
      return false if first_is_zero

      fs_sum = @factors.inject(0) do |r,s|
         r + s.scan(/./).map do |c|
            t[@chars.index(c)].to_s
         end.join.to_i
      end
      sum = @sum.scan(/./).map {|c| t[@chars.index(c)].to_s }.join.to_i
      return false unless fs_sum == sum

      @table = Hash[*(@chars.zip(t).flatten)]
   end
end

VerbalArithmetic.new(ARGV[0]).table.each do |k,v|
   puts "#{k}: #{v}"
end

構文解析適当。