CodeIQ:カードを使って数を作ろう(菅単編)

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「カードを使って数を作ろう(簡単編)」(CodeIQ)を参照してください。

問題の概要

問題を引用します。

【概要】
カードが何枚かあります。
カードには、一桁の数字が書いてあります。
カードをn枚選んで並べて数を作ります。
たとえば、「3」「1」「4」「1」「5」が書いてあるカードから 3枚(つまり、n=3)を選んで並べると「341」や「115」を作ることができます( 「334」や「31」は作れません)。
作れる数のうち、ある数 m にもっとも近い数を答えてください。

【入出力】

入力は
4,1234,1/2/2/3/9/9/9
のような感じです。
コンマ区切りで、n(カードの枚数)、m(この値に近い数を作る)、カード列 が並んでいます。
カード列は、カードに書いてある数字をスラッシュ区切りで並べたものです。

出力は、
1232
のような感じです。最も近い数が2つある場合は、小さい順に両方共出力してください。
1233,1235
のような感じです。
末尾の改行はあってもなくても構いません。
作れる数がひとつもない場合には、-を出力してください。

【補足】
「01」のような、「0」で始まる二桁以上の数は作れません。一桁の「0」はOKです。
カードの枚数は8枚以下です。
n は 1以上で、カードの枚数を超えることはありません。
m は 0以上、10の9乗以下です。
6 のカードを逆さにして 9 として使ったりすることはできません。
不正な入力に対処する必要はありません。

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

def toNum(ptn)
	if (ptn.size > 1) && (ptn.last == 0) then return -1 end

	ret = 0
	ptn.each_with_index{|n, i|
		ret += n * (10**i)
	}
	return ret
end

def solve(n,m,cards)
	ret = []
	bs = 9999999999
	cards.permutation(n){|ptn|
		v = toNum(ptn)
		if v<0 then next end

		s = (m-v).abs
		if s < bs then
			ret = [v]
			bs = s
		elsif s == bs then
			ret << v
		end
	}

	return ret.uniq.sort
end

def printResult(ptn)
	if ptn.empty? then
		puts("-")
	else
		puts(ptn.join(","))
	end
end

while line = gets
	line.strip!
	if line.empty? then next end

	sc = line.split(",")

	n = sc[0].to_i
	m = sc[1].to_i
	cards = sc[2].split("/").map{|i| i.to_i}

	ret = solve(n,m,cards)
	printResult(ret)
end

解説

問題に簡単と書かれている通りそれほど難しくありません。
計算量も8!しかないので総当たりで大丈夫です。単純に全パターン列挙するので入力値mに近いかどうかはmと列挙した値の差の絶対値でチェックすれば大丈夫です。

solve()

引数n,m,cardsは入力値をパースして数値にしたものです。
cardsは[1,2,2,3,9,9,9]のようなカードの値の配列です。
Rubyなのでcardsにpermutation()を使ってパターンを列挙します。
そしてtoNum()でパターンを数値に変換します。toNum()はエラー値として-1を返すので-1が帰った時は無視します。
そして帰ってきた値(v)とmの差の絶対値を取り、以前の差の絶対値より小さくなっていたら結果を更新します。以前の差の絶対値と同値なら結果に追加します(20〜26行目)。
結果は同じ値が重複していたり、順番が昇順になっていないのでuniqしてsortしてから返します。

toNum()

パターンは配列でくるので1つずつを各桁の値として数値に変換します(7〜9行目)。なお、プログラムでは配列の最初の桁が1の位、2つ目が10の位、…、となります。
ただし、1桁の値でない限り戦闘に0はおけないという仕様なので4行目でチェックして先頭(配列の最後の要素)が0ならエラーリターンします。

雑感

これを書いている時に気付きましたが、パターンを数値に直すのは配列をcardsを数値に変換せず文字列のまま扱って、joinしてto_iすれば済んでいました。Rubyを始めてから結構Rubyでプログラムを書いたと思いますが、頭の中で考えるコードはC/C++とJavaに近い言語のままのようです。