CodeIQ:正二十面体の隣の面

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「正二十面体の隣の面」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
【概要】
展開すると右図のようになる正二十面体があります。


面の番号を指定します。
その面の、隣(つまり、辺を共有する)面の番号を昇順にコンマ区切りで出力して下さい。

【入出力】
入力は
2
のようになっています。
面の番号を示す 1〜20 のいずれかの数です。

出力は2 の隣の面の番号を昇順にコンマ区切りで出力すればよいので
1,3,7
のような感じです。

【例】
入力出力
2 1,3,7
10 5,14,15

【補足】
不正な入力に対処する必要はありません。

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

DICE = [
	[  1, nil,   2, nil,   3, nil,   4, nil,   5, nil],
	[  6,  11,   7,  12,   8,  13,   9,  14,  10,  15],
	[nil,  16,  nil, 17, nil,  18, nil,  19, nil,  20],
]

def solve(n)
	y = -1
	x = -1

	catch(:found){
		for y in 0...DICE.size
			for x in 0...DICE[y].size
				throw :found if n == DICE[y][x]
			end
		end
	}

	u = (y-1) >= 0 ? (y-1) : DICE.size-1
	d = (y+1) < DICE.size ? (y+1) : 0

	if y == 0 then
		l = (x-2) >= 0 ? (x-2) : DICE[y].size-2
		r = (x+2) < DICE[y].size ? (x+2) : 0
	elsif y == 1
		l = (x-1) >= 0 ? (x-1) : DICE[y].size-1
		r = (x+1) < DICE[y].size ? (x+1) : 0
	elsif y == 2
		l = (x-2) >= 0 ? (x-2) : DICE[y].size-1
		r = (x+2) < DICE[y].size ? (x+2) : 1
	end

	return [DICE[u][x], DICE[d][x], DICE[y][l], DICE[y][r]].compact.sort
end

# main
while line = gets
	line.strip!
	next if line.empty?

	puts solve(line.to_i).join(",")
end

解説

特に難しい部分はなく、展開図を二次元配列で表現して上下左右を求めればできます。

考え方

展開図を二次元配列で表現します。

[
 [ 1, nil, 2, nil, 3, nil, 4, nil, 5, nil],
 [ 6, 11, 7, 12, 8, 13, 9, 14, 10, 15],
 [nil, 16, nil, 17, nil, 18, nil, 19, nil, 20],
]

ポイントは最上段と最下段の空き部分をnilにしておくことくらいでしょうか。
あとはこの表から指定された番号の上下左右の値を取得し、nilを除いてソートすればOKです。

main

DICEは20面体のの展開図を二次元配列で表現したものです。
入力値を数値にしてsolve()に渡します。
結果が配列で帰るのでjoin()して印字します。

solve(n)

問題を解きます。
xとyは入力値の座標です。

13〜19行目で入力値の座標を求めます。

21行目で上、22行目で下の値を求めます。上は配列の添え字が0未満になったら3行目、下は配列の添え字が2を超えたら1行目になるのでそれも考慮します。

24〜26行目は1行目の左右、27〜29行目は2行目、30〜32行目は3行目の左右を求めます。1行目と3行目は隣がnilの場合さらにその隣が隣接する面になるのでそれを考慮します。
両端を超えたら反対側になります。

結果からnilをcompact()で除いて、sort()して結果を返します。

雑感

単純な方法ですが十分と思います。
ゲームに使うちなみに、12面体のダイスを眺めてみましたが、配列はこの問題とは違いました。