Dive Into Python 3の覆面算をRubyで書いてみた

はじめに

最近、必要に迫られてDive Into Python 3を読んでPythonの勉強をしているわけですが、その第8章で出てきた覆面算のコードをRubyで書いてみた。

実装

以下の通りです。なるべくオリジナルに近づけたつもり。

# encoding: utf-8
DOCUMENT = <<EOS
Find solutions to alphametic equations.

> Alphametics.solve("SEND + MORE == MONEY")
"9567 + 1085 == 10652"
EOS

module Alphametics
  def solve(puzzle)
    words = puzzle.upcase.scan(/[A-Z]+/)
    unique_characters = words.join.split("").uniq
    raise "Too many letters" unless unique_characters.size <= 10

    first_letters = words.map{|word| word.slice(0)}.uniq
    n = first_letters.size 
    characters = (first_letters + (unique_characters - first_letters)).join

    solution = (0..9).to_a.permutation(characters.length) do |guess|
      next if guess.slice(0, n).include?(0) 
      equation = puzzle.tr(characters, guess.join)
      break equation if eval(equation)
    end

    solution if solution.instance_of?(String)
  end
  module_function :solve
end

if $0 == __FILE__
  puts DOCUMENT and return if ARGV.size.zero?

  ARGV.each do |puzzle|
    puts puzzle
    solution = Alphametics.solve(puzzle)
    puts solution unless solution.nil?
  end
end

実行例

$ ruby alphametics.rb "SEND + MORE == MONEY"
SEND + MORE == MONEY
9567 + 1085 == 10652

感想

Pythonは要素の重複を許さない「集合」というクラスがあるので、いちいち重複を取り除く必要がないのがいい。*1
でも、"".join(list)とか見てると完全に逆だろって思う。*2
いや、あれか、レシーバーに対するメソッドの戻り値はレシーバーと同じ型でなければいけない、という思想なのか。

参考

*1:Rubyでもsetっていうライブラリーがあるので、これを使えば同様のことはできる。

*2:Rubyなら、同様のコードは list.join("") となる。