DevQuizに参加しました(3)
スライドパズル
最後はスライドパズル。いわゆる15パズルを解く問題なのですが、4x4だけではなく3x3〜6x6、4x3などの長方形もあり、「壁」という動かないパネルもあるという、変形15パズルです。
これはチャレンジ問題ということで、とても難しかったです。
結果、5000問中1335問正解で時間切れになってしまいました。
この問題は反省しきりです。
module Main where import Prelude hiding (Left, Right) import Time import Directory sampletable :: Table sampletable = ((0, 0), [(1, (1, 0)), (2, (1, 1)), (3, (0, 1))]) samplegoal :: Table samplegoal = ((1, 1), [(1, (0, 0)), (2, (1, 0)), (3, (0, 1))]) -- solve001 = moves2str $ solve (5, 6) $ readTable 5 "12=E4D9HIF8=GN576LOABMTPKQSR0J" -- solve007 = moves2str $ solve (3, 3) q007 q007 :: Table q007 = readTable 3 "168452=30" {- q007 = ((2, 2), [ (1, (0, 0)), (2, (2, 1)), (3, (1, 2)), (4, (0, 1)), (5, (1, 1)), (6, (1, 0)), (8, (2, 0)) ]) -} main = do start <- getClockTime putStrLn $ show start udlf <- getLine limitstr <- getLine readPuzzles (read limitstr) 1 end <- getClockTime putStrLn $ show $ diffClockTimes end start readPuzzles limit count = do wid <- getNum hig <- getNum puz <- getLine if wid + hig <= 9 -- if wid <= 3 && hig <= 3 then do nam <- return ("ans" ++ show count) exist <- doesFileExist nam if exist then return () else do putStrLn nam (moves, ccount) <- return $ solve (wid, hig) $ readTable wid puz appendFile ("ans" ++ show count) $ moves2str $ moves putStrLn $ show ccount else do return () if count == limit then return () else readPuzzles limit (count + 1) getNum = do str <- getNumStr return $ read str getNumStr = do c <- getChar if c == ',' then return "" else do l <- getNumStr return (c:l) readTable width tstr = (take0, dropwall) where take0 = snd $ head $ filter isZeroPanel panels isZeroPanel (val, _) = val == 0 dropwall = filter isNomalPanel panels isNomalPanel (val, _) = val > 0 panels = map mkPanel $ zip tstr [0..] mkPanel (ch, ipos) = (char2id ch, (ipos `mod` width, ipos `div` width)) char2id ch = (length $ takeWhile ((/=) ch) "=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") - 1 moves2str :: [[Move]] -> String moves2str mss = unlines $ map (map move2char) mss move2char :: Move -> Char move2char Left = 'L' move2char Right = 'R' move2char Up = 'U' move2char Down = 'D' type Pos = (Int, Int) type Panel = (Int, Pos) type Table = (Pos, [Panel]) type Size = (Int, Int) data Move = Left | Right | Up | Down deriving (Show, Eq) data CanMove = None | Can Move deriving (Show, Eq) type Solution = ((Table, Integer), [Move]) type SolveState = ([Integer], [Solution], [Solution]) giveup = 200 toprank = 30 canmove :: Pos -> Pos -> CanMove canmove (x0, y0) (x1, y1) | x0 - 1 == x1 && y0 == y1 = Can Left | x0 + 1 == x1 && y0 == y1 = Can Right | x0 == x1 && y0 - 1 == y1 = Can Up | x0 == x1 && y0 + 1 == y1 = Can Down | otherwise = None filtpanel :: Table -> [(Move, Panel)] filtpanel (_, []) = [] filtpanel (pos, (p:ps)) | cm p == Can Left = (Left , p) : nr | cm p == Can Right = (Right, p) : nr | cm p == Can Up = (Up , p) : nr | cm p == Can Down = (Down , p) : nr | cm p == None = nr where cm (_, posn) = canmove pos posn nr = filtpanel (pos, ps) move :: Table -> Panel -> Table move (zpos, panels) (tch, tpos) = (tpos, exchange panels (tch, zpos)) exchange :: [Panel] -> Panel -> [Panel] exchange ps target = map changeone ps where changeone org | fst org == fst target = target | otherwise = org solve :: Size -> Table -> ([[Move]], Integer) solve size inittable = (map reverse $ fst resultsolve, snd resultsolve) where goalhash = makeGoalHash size inittable resultsolve = solve2 0 goalhash size ([], [((inittable, makeHash size inittable), [])], []) solve2 :: Integer -> Integer -> Size -> SolveState -> ([[Move]], Integer) solve2 count goalhash size (hashes, ss, ss0) | count > giveup = ([], count) | restsolve == 0 = ([], count) | getgoals == [] = solve2 (count + 1) goalhash size nextsolvestate | otherwise = (getgoals, count) where restsolve = length (ss ++ ss0) getgoals = snd $ unzip $ filter (\((_, h), _) -> h == goalhash) ss nextsolvestate = (nexthashes, newsolutions, waitedsolutions) nexthashes = hashes ++ (snd $ unzip $ fst $ unzip ss) newsolutions = makenext size nexthashes $ take toprank sortedsolutions waitedsolutions = drop toprank sortedsolutions sortedsolutions = sortbyrank size (ss ++ ss0) makeGoalHash :: Size -> Table -> Integer makeGoalHash size (_, ps) = sum $ map (goalhashval size) ps goalhashval :: Size -> (Int, Pos) -> Integer goalhashval size (val, _) = (toInteger mulsize ^ val * (toInteger val)) where mulsize = fst size * snd size makeHash :: Size -> Table -> Integer makeHash size (_, ps) = sum $ map (hashval size) ps hashval :: Size -> (Int, Pos) -> Integer hashval size (val, pos) = (toInteger mulsize ^ val * (toInteger $ getGoalValue size pos)) where mulsize = fst size * snd size sortbyrank :: Size -> [Solution] -> [Solution] sortbyrank size ss = snd $ unzip $ qsort $ map withrank ss where withrank s = (ranktable size $ fst $ fst s, s) qsort [] = [] qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = [a | a <- xs, fst a <= fst x] larger = [b | b <- xs, fst b > fst x] ranktable :: Size -> Table -> Int ranktable size (_, ts) = foldr (distance size) 0 ts distance :: Size -> Panel -> Int -> Int distance (w, h) (val, (x, y)) sum = sum + abs((val - 1) `mod` w - x) + abs((val - 1) `div` w - y) getGoalValue :: Size -> Pos -> Int getGoalValue (w, _) (x, y) = x + y * w + 1 makenext :: Size -> [Integer] -> [Solution] -> [Solution] makenext size hashes ss = concatMap (\((t, h), ms) -> makeonenext size hashes (t, ms)) ss makeonenext :: Size -> [Integer] -> (Table, [Move]) -> [Solution] makeonenext size hashes (table, moves) = filter (\((_, h), _) -> not $ loopsolution hashes h) $ map process $ filtpanel table where process (m, p) = ((nexttable, makeHash size nexttable), m : moves) where nexttable = move table p loopsolution :: [Integer] -> Integer -> Bool loopsolution hashes newhash = any ((==) newhash) $ hashes
Haskellで解きました。
- まずは幅優先の総当り(大きいパズルでは全然)
- ゴールへの近さを定義して、近いものから優先して解く
- ハッシュを計算して盤の比較
- 解けた問題の解答はファイルに保存して2度解かないようにする
- 時間がかかったらギブアップして次の問題へ行く
てな感じで上記のコードになりました。
反省点
- 解く速さにこだわってしまい、本格的に計算を始めるのが遅れた
- そのせいで時間切れ
- ギブアップなど、早く解くこと以外に力を入れ解ける問題をさっさと済ませればよかった
- あんま関数型っぽく解けなかった気がする
- データ型を木構造にするなどもうちょっと凝れば、効率もが上がったかもしれない
なんとなく自分の仕事のやり方の問題点も顕になってしまった気もする。
自分の仕事のやり方を見直す意味でもこういう機会はちょくちょく作りたい。