ルービックキューブ解法プログラム
以前書いたHaskellでルービックキューブを解くプログラム。
但しルービックキューブになっておらず、2×2×2でしかも、1つのキューブに本当は
角で3面あるが、ここでは面の概念がなく、その位置のみを扱っている。
更に、出てきた「解」が本当に正しいのかの検証をしていないので
もしかしたら間違っているかも知れない。
------------------------------------------------- -- rubik.hs -- ルービックキューブ解法 ------------------------------------------------- -- 単位ベクターから指定されたベクターへの変換を見つける。 -- > search [2,1,3,4,5,6,7,8] {- 1.場所とコマ 場所は空間的な位置を示す。実際にその場所を占めるコマには因らない。 コマは実際の要素を表す。コマは何れかの場所にいる。 場所の指定。以下のインデックスで場所を指定する。 これが、リストの順番となる。ぞれぞれの場所をL1,L2,..L8で表す。 L4 L3 L1 L2 L8 L7 L5 L6 コマはそれぞれ、C1,C2,..,C8で表す。 コマが占めるべき正しい位置はC1はL1、C2はL2、... C8はL8となる。 2.回転 回転演算はそれぞれの面に対して90度時計回りに回転することを基本演算とする。 可能な面は6面(以下)あるため、基本回転は6種類ある。 右側面, 左側面, 上面, 下面, 前面, 後面 但し、1つのコマの位置を固定して考えることが出来る。 この場合、回転は以下の3種類に絞れる。 右面、上面、全面 実際の変換は以下のようになる。 上面 [C1, C2, C3, C4, C5, C6, C7, C8] -> [C2, C3, C4, C1, C5, C6, C7, C8] 右面 [C1, C2, C3, C4, C5, C6, C7, C8] -> [C1, C6, C2, C4, C5, C7, C3, C8] 前面 [C1, C2, C3, C4, C5, C6, C7, C8] -> [C5, C1, C3, C4, C6, C2, C7, C8] 3.回転代数 回転の演算子は行列で表現される。 ある回転とある回転を連続して作用させることは、行列の積算で表現される。 ある回転の反転はその行列の逆行列で表現される。 行列の要素は0および1のみから構成される。 行列のある要素が1であれば、その同一列、同一行には1は存在しない。 逆行列は転地行列に等しい。 逆行列は逆回転を表す。 コアのある配置は1つの行列で表現できる。 即ち、eをコマが揃った状態とする。e = [1,2,3,4,5,6,7,8] ある配置を x = [x1,x2,x3,x4,x5,x6,x7,x8] とする。 すると、この2つの配置を変換する1つの行列が存在する。 即ち、1つの行列で全ての可能な変換を表現することができる。 単位行列は上記の3つである。これは実際に操作可能な変換を意味する。 問題は任意の行列(配置から配置への変換)を単に行列の積として表現することである。 可能なコマの配置は7!個ある(相対的位置関係だけが有意なので1つは固定して考えられる)。 即ち全部で5040個。このうちコマが揃った状態が1つ存在する。e = [1,2,3,4,5,6,7,8] -} -- 型定義 type Vector = [Int] type Operator = [[Int]] -- 基本演算子 opT :: Operator opT = [[0,1,0,0,0,0,0,0], [0,0,1,0,0,0,0,0], [0,0,0,1,0,0,0,0], [1,0,0,0,0,0,0,0], [0,0,0,0,1,0,0,0], [0,0,0,0,0,1,0,0], [0,0,0,0,0,0,1,0], [0,0,0,0,0,0,0,1]] opR :: Operator opR = [[1,0,0,0,0,0,0,0], [0,0,0,0,0,1,0,0], [0,1,0,0,0,0,0,0], [0,0,0,1,0,0,0,0], [0,0,0,0,1,0,0,0], [0,0,0,0,0,0,1,0], [0,0,1,0,0,0,0,0], [0,0,0,0,0,0,0,1]] opF :: Operator opF = [[0,0,0,0,1,0,0,0], [1,0,0,0,0,0,0,0], [0,0,1,0,0,0,0,0], [0,0,0,1,0,0,0,0], [0,0,0,0,0,1,0,0], [0,1,0,0,0,0,0,0], [0,0,0,0,0,0,1,0], [0,0,0,0,0,0,0,1]] -- 正解ベクター eV :: Vector eV = [1,2,3,4,5,6,7,8] ------------------------------------------------- -- 行列のリストを先頭から掛け合わせていく。 ------------------------------------------------- mulOpList :: [Operator] -> Operator mulOpList [] = [] mulOpList (x:xs) | xs == [] = x | otherwise = mulOpList (mulOp (head xs) x : (tail xs)) ------------------------------------------------- -- ベクターに行列のリストを作用させる。 -- これは上記のmulOpListを用いても実装可能であるが、 -- あえて別の方式で実現している。 -- 即ち、ベクタに順に行列を作用させていく。 ------------------------------------------------- opList :: Vector -> [Operator] -> Vector opList x [] = x opList x (y:ys) = opList (rotate y x) ys opListMul :: [Operator] -> Operator opListMul (x:xs) = opListMul ((mulOp x (head xs)) : (tail xs)) ------------------------------------------------- -- y = A x (x,yはn次元ベクター。Aはn*n行列) -- 回転演算を行う。 -- ベクターの行列式による演算として実現する。 ------------------------------------------------- rotate :: Operator -> Vector -> Vector rotate m v = map (op1 v) m -- ベクターに演算を行う。 -- 演算子ベクターは唯1つの要素のみが1になっており、その他は0である。 -- 演算子の1になっている個所の要素をベクターから抽出する。 -- ex.) op1 [a1,a2,a3,a4,a5] [0,0,1,0,0] => a3 op1 :: Vector -> [Int] -> Int op1 _ [] = 0 op1 (x:xs) (y:ys) | y == 1 = x | otherwise = op1 xs ys ------------------------------------------------- -- C = A * B (A,B,Cはn*n行列) -- 行列の掛け算を行う。 ------------------------------------------------- mulOp :: Operator -> Operator -> Operator mulOp x y = mulr x (invertOp y) mulr :: [[Int]] -> [[Int]] -> [[Int]] mulr [] _ = [] mulr (x:xs) y = (mul1 x y) : (mulr xs y) mul1 :: [Int] -> [[Int]] -> [Int] mul1 _ [] = [] mul1 x (y:ys) = (mulElem x y) : mul1 x ys mulElem :: [Int] -> [Int] -> Int mulElem [] [] = 0 mulElem (x:xs) (y:ys) | (x == 1) && (y == 1) = 1 | otherwise = mulElem xs ys ------------------------------------------------- -- 行列の行と列を入れ替える。 -- 行と列の入れ替えは、逆行列を意味する。 ------------------------------------------------- invertOp :: [[Int]] -> [[Int]] invertOp x = rotate_nth 0 (length x) x rotate_nth :: Int -> Int -> [[Int]] -> [[Int]] rotate_nth n max x | n == max = [] | otherwise = (take_nth n x) : (rotate_nth (n+1) max x) take_nth :: Int -> [[Int]] -> [Int] take_nth n x = map (nth_element n) x nth_element :: Int -> [Int] -> Int nth_element n x = x!!n ------------------------------------------------- -- 探索 ------------------------------------------------- -- データ型定義 -- 3つの基本演算を行列ではなく、シンボルで表現している。 ------------------------------------------------- data Op = T | R | F deriving (Eq, Show) ------------------------------------------------- -- 変換の解を求める。 -- 単位ベクターから特定のベクターへの変換を行う -- 回転の並びを出力する。 -- 可能な回転を全て出力する(どこかで停めないと無限ループする)。 ------------------------------------------------- search :: Vector -> [[Op]] search x = filter (match x) allOp match :: Vector -> [Op] -> Bool match v x = same v (ol x) ------------------------------------------------- -- 全ての演算子の並びを出力する。 -- 当然、「全て」の並びなので無限に存在する。 -- 即ち出力されるリストは無限リストである。 -- 但し、1回転して元に戻るような演算子の並びも出力されてしまう。 -- なので、非常に重複が多い。 ------------------------------------------------- allOp = listOp [[T],[R],[F]] listOp :: [[Op]] -> [[Op]] listOp x = x ++ (listOp (makeOps x)) makeOps :: [[Op]] -> [[Op]] makeOps x = concat (map addDim x) addDim :: [Op] -> [[Op]] addDim xs = [x:xs| x <- [T,R,F]] ------------------------------------------------- -- 単位ベクターを演算子のリストに適用させる。 ------------------------------------------------- ol :: [Op] -> Vector ol xs = opList eV (map toMatrix xs) toMatrix :: Op -> Operator toMatrix x | x == T = opT | x == R = opR | x == F = opF ------------------------------------------------- -- 2つの配列が同一かどうか調べる。 -- 同一であれば True、異なっていれば False ------------------------------------------------- same :: [Int] -> [Int] -> Bool same [] [] = True same (x:xs) (y:ys) | x /= y = False | otherwise = same xs ys