CodeIQ:正六角形ブロックの回転

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「正六角形ブロックの回転」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
【概要】
fig.1 右図のような、正六角形マス目の集まりがあります。
マス目のうちのいくつかにブロックが入っています。
中心を指定するので、そこを中心にブロックを時計回りに 120度回したらどうなるのかを計算して下さい。

【入出力】
入力は
a 000/0110/00000/0000/000
のようになっています。

空白の前は中心を示す記号です。
a, b のいずれかで、右図の a点、b点に対応しています。

空白のあとは、マス目の状況を示しています。
スラッシュ区切りで各行の状況を上から順にならべています。
「1」がブロックあり、「0」がブロックなしのマスを示します。

出力は、回転したあとのブロックの状況を
000/0000/00000/0100/100
のような感じで。
入力の時と同様、各行の状況を上から順にスラッシュ区切りでならべて下さい。

ただし、
b 111/0101/00000/0100/111
のように、回転するともとのマス目からはみ出してしまう場合は
-
を出力して下さい。

【例】
入力出力状況
a 000/0110/00000/0000/000 000/0000/00000/0100/100 fig.2fig.3
b 111/0101/00000/0100/111 - fig.4N/A
b 010/0011/01010/0100/000 011/0000/00111/0010/000 fig.5fig.6

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

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

ROT_A = [
	[[3,2], [4,1], nil],
	[[2,2], [3,1], [4,0], nil],
	[[1,1], [2,1], [3,0], nil, nil],
	[[1,0], [2,0], nil, nil],
	[nil, nil, nil],
]

ROT_B = [
	[nil, [2,4], [3,3]],
	[nil, [1,3], [2,3], [3,2]],
	[nil, [0,2], [1,2], [2,2], [3,1]],
	[nil, [0,1], [1,1], [2,1]],
	[nil, [0,0], [1,0]]
]

def getPos(line)
	ret = []
	line.split("/").each_with_index{|l, i|
		l.chars.each_with_index{|c, j|
			ret << [i,j] if c.to_i == 1
		}
	}
	return ret
end

def rotate(pos_list, map)
	rmap = [
		[0,0,0],
		[0,0,0,0],
		[0,0,0,0,0],
		[0,0,0,0],
		[0,0,0]
	]

	for pos in pos_list
		nxt = map[pos[0]][pos[1]]
		return "-" if nxt == nil

		rmap[nxt[0]][nxt[1]] = 1
	end

	ret = []
	for r in rmap
		ret << r.join("")
	end

	return ret.join("/")
end

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

	c, m = line.split
	pos = getPos(m)
	if c == "a" then ret = rotate(pos, ROT_A)
	elsif c == "b" then ret = rotate(pos, ROT_B)
	end

	puts ret
end

解説

プログラムというより手計算です。

考え方

座標の回転です。
三角関数はほとんど忘れてしまいましたが、行列と三角関数を使って座標の回転ができることは覚えていました。なので調べてみればすぐわかるのですが、うまくゆきません。
6角形を2次元配列にマッピングして回転させると回転させたあと、綺麗に整数にならないのです。
何かうまい方法があるんだろうな、とは思いますが思いつきませんでした。

盤面のサイズが小さいので紙に書いてどこからどこに移動するかの変換テーブルを作ってごまかしました。

main

入力値をrotate()に渡し、結果を印字します。
定数ROT_Aはaを中心に回転した場合、ROT_Bはbを中心に回転した場合にどこからどこに移動するかを示す変換テーブルです。左上を(0,0)、右下を(4,2)として入力値の場所の値がどの座標に移動するかを示します。nilは範囲外に出てしまうことを表します。
例えばaを中心に回転した場合、左上(0,0)は(3,2)の位置に移動します。

getPos(line)

入力値からブロックのある座標のリストを取得します。
'/'で分割して分割後の文字列を1文字ずつチェックし、1だったらその座標を返却値の配列に追加して返すだけです。

rotate(pos_list, map)

引数pos_listはgetPos()で取得したブロックの座標リスト、mapはROT_AかROT_Bです。
rmapは回転後のブロックの位置を記録する領域で、初期値は全ての値を0とします。

pos_listの座標について要素ごとにループし、mapの当該箇所の値を取得します。値がnilならはみ出すので'-'を返却します。そうでなければ取得した座標についてrmapの値を1にします。

rmapの各行について連結して文字列とし、さらにそれを'/'で連結して答えを返却します。

雑感

多分計算だけでできるんだろうと思います。
任意の座標だったらできなかった可能性が高いです。