切符問題
某プログラム勉強会で、お題が出たので解いてみる。
遠い昔に似た問題のソルバーを作ったことがある。
いくつかの正の整数と加算・減算・乗算 の3つの演算子(そして、括弧)を使って、ある別の正の整数を生成する方法を探し出すプログラムを書け。
例えば。{2,5,6,8}から10を生成する方法は、例えば (2+8)*(6-5) があるので、これを出力すればよい。
なお:
・解がない場合は、その旨を出力すること
・解が複数ある場合でも、ひとつだけ出力すればよい
・オーバーフローは考慮しなくてもよい
・不正な入力(負の数がある、など)への対処は不要
・括弧は無駄にたくさんついていてもよい。
・ヒント:再帰
「切符問題」といわれていたけれど、与えられた4つの数字を加減乗除して指定された数字にする、という問題。
切符に数字が4つ書かれていて、その数字を使ったのが起源だったようだが。
見かけた車のナンバーを加減乗除して10を作るのは、いい暇つぶし。
で、出題者が再帰での回答を期待しているようなので、あえて再帰をしない方法で。
require "mathn" class Ticket def initialize(val, elem) @val = val @elem = elem end def calc_rpn(rpn) stack = [] rpn.each do |e| stack.push e.class == Fixnum ? e : "(#{stack.pop}#{e}#{stack.pop})" end return nil unless stack.size == 1 val = eval(stack.first) rescue val = nil return nil unless val == @val return stack.first end def solve %w(+ - * /).map{|e| [e]*(@elem.size - 1)}.flatten.combination(@elem.size - 1).to_a.uniq.each do |e| (@elem + e).permutation do |rpn| if ans = calc_rpn(rpn) return ans end end end "解なし" end end val, *elem = ARGV.map(&:to_i) puts Ticket.new(val, elem).solve
与えられた数から、逆ポーランド記法のパターンを生成して指定された数になるかチェックしています。
かなり強引な方法で無駄が多いので、与える数の個数は4個が限界でしょうか。
普通に再帰を使うとこんな感じ。
require "mathn" class Ticket def initialize(val, elem) @val = val @elem = elem end def gen_exp(elem,&block) if elem.size == 1 yield elem.first else 0.upto(elem.size-2) do |i1| e1 = elem[i1] (i1+1).upto(elem.size-1) do |i2| e2 = elem[i2] elem_next = [] elem.each_with_index{|e,i| elem_next.push(e) unless i == i1 or i == i2} gen_exp(elem_next+["(#{e1}+#{e2})"],&block) gen_exp(elem_next+["(#{e1}-#{e2})"],&block) gen_exp(elem_next+["(#{e1}*#{e2})"],&block) gen_exp(elem_next+["(#{e1}/#{e2})"],&block) gen_exp(elem_next+["(#{e2}-#{e1})"],&block) gen_exp(elem_next+["(#{e2}/#{e1})"],&block) end end end end def solve gen_exp(@elem) do |exp| val = eval(exp) rescue val = nil return exp if val == @val end "解なし" end end val, *elem = ARGV.map(&:to_i) puts Ticket.new(val, elem).solve
ここで、出題をよく読むと「加減乗除」の「除」は使わなくて良いらしい。
有理数クラスが無いので見送っていたscalaの出番(scalaなら有理数クラスの実装も簡単なんだけれどね)。
object Ticket { import scala.collection.mutable.Stack def main(args: Array[String]) { args.map(_.toInt).toList match { case x::xs => if (!solve(x, xs, List())) println("No answer") case _ => println("Argument error") } } def calcRpn(rpn:List[Any]):Int = { var st = new Stack[Int] rpn.foreach { _ match { case "+" => st.push(st.pop+st.pop) case "-" => st.push(-st.pop+st.pop) case "*" => st.push(st.pop*st.pop) case n:Int => st.push(n) } } st.pop } def solve(ans:Int, nums:List[Int], pn:List[Any]):Boolean = { var c1, c2 = 0 pn.foreach {_ match{case n:Int => c1+=1; case _ => c2+=1}} if (nums.size == 0) { if (c1 == c2 + 1) { if (calcRpn(pn.reverse) == ans) { println(pn.reverse) return true } return false } } else { for (i <- 1 to nums.size) { var (n1,n2) = nums.splitAt(i) if (solve(ans, n1.take(n1.size-1):::n2, n1.last::pn)) return true } } if (c1 > c2 + 1) { List("+", "-", "*").foreach {op => if (solve(ans, nums, op::pn)) return true } } false } }