digging with bing
Table of Contents
digging with ChatGPT
「AIだからすごいんでしょ」 うーん、アルゴリズムの表現の場合は、どうなんでしょ?
前回からやってるChatGPTに依頼した迷路作成だが、永久ループの泥沼から脱 出出来ずに、無理を指示しちゃった。AIと言えども、ヒントを与えないと頭を 働かせられない。debuggerを使って、堂々巡りしてる所を発見し、そこを改善 させたのさ。その結果が、これだ。
ghci> :main ########## # # # # # ########## # # # # # ########## # # # # # ########## # # # # # ########## # # # # #
迷路失格である。原因と思う所は、配列の範囲外をアクセスしちゃう部分の防 止策。なんだかなあって方法で回避してたぞ。今となっては、それを追求する 気にもなれない。穴掘り方法だけを上げておく。
dig :: Maze -> (Int, Int) -> StdGen -> IO () dig maze (x, y) gen = do let directions = shuffle [minBound..maxBound] gen dig' maze (x, y) directions gen dig' :: Maze -> (Int, Int) -> [Direction] -> StdGen -> IO () dig' maze _ [] _ = return () dig' maze (x, y) (dir:dirs) gen = do let nextCell = move dir (x, y) wallCell = move (opposite dir) nextCell isWallCellVisited <- readArray maze wallCell isNextCellVisited <- readArray maze nextCell -- 新しく追加 if isWallCellVisited == wall || isNextCellVisited == path -- 新しく追加 then dig' maze (x, y) dirs gen else do writeArray maze nextCell path writeArray maze wallCell path dig maze nextCell gen let remainingDirs = delete dir dirs randomIndex <- randomRIO (0, length remainingDirs - 1) let nextDir = remainingDirs !! randomIndex dig' maze nextCell (delete nextDir remainingDirs) gen
何だかなあの結末。
digging with bing
ならばbingではどうよ。セカンド・オピニオンしてみる。
穴掘り法を利用して、迷路を作成して下さい。出力言語はhaskellでお願いします。 盤の構造には、Array.IOを使用して下さい。
haskell maze digging method を検索しています
上の質問が、こんな風に要約されてるみたい。で、bingは何を参考書にしたん だやら。こんなコードを提示してきた。ああ、勿論オイラーが悪い所は修正し ましたよ。特にshuffleなんて出鱈目のいいところだったので、書き換えた。
それから、質問の回数が以前は5回だったんだけど、今回は10回に増量されて た。みんなオマケが大好きだからなあ。煎餅が1枚増量なんて言われると、い ちもにもなく飛び付きますからなあ。
import System.Random import Data.Array.IO import Control.Monad import Debug.Trace type Maze = IOUArray (Int, Int) Char maze :: Int -> Int -> IO Maze maze rows cols = newArray ((0, 0), (rows - 1, cols - 1)) '#' dig :: Maze -> (Int, Int) -> IO () dig maze (row, col) = do writeArray maze (trace("(row,col)=" ++ show (row,col)) (row, col)) ' ' directions <- shuffle [(0, -1), (-1, 0), (0, 1), (1, 0)] forM_ directions $ \(r, c) -> do let loc = (row + r * 2, col + c * 2) okP <- liftM2 (&&) (inRange <$> getBounds maze <*> pure loc) (((==) '#') <$> readArray maze loc) when okP $ do writeArray maze (r + row, c + col) ' ' dig maze loc shuffle :: [a] -> IO [a] shuffle xs = shuffle' xs [] where shuffle' [] ys = return ys shuffle' zs ys = do index <- randomRIO (0, length zs - 1) let elem = zs !! index zs' = take index zs ++ drop (index + 1) zs shuffle' zs' (elem : ys) printMaze :: Maze -> IO () printMaze maze = do ((minRow, minCol), (maxRow, maxCol)) <- getBounds maze forM_ [minRow..maxRow] $ \row -> do forM_ [minCol..maxCol] $ \col -> do putChar =<< readArray maze (row, col) putChar '\n' main :: IO () main = do let rows = 21 -- odd number to make the algorithm work properly. cols = 21 -- odd number to make the algorithm work properly. m <- maze rows cols dig m (1, 1) printMaze m
で、結果。
ghci> main (row,col)=(1,1) *** Exception: Error in array index ghci> main (row,col)=(1,1) (row,col)=(1,3) (row,col)=(3,3) (row,col)=(3,1) *** Exception: Error in array index
なんだかなあ、だ。
start debug
okP <- liftM2 (&&) (inRange <$> getBounds maze <*> pure loc) (((==) '#') <$> readArray maze loc)
指定されたlocが配列の範囲内になってるなら、そこからreadして、壁なら満 足って理解した。評価の都合で、先にreadしちゃったら、ドカンとなる可能性 が有るな。
okP <- liftM2 (&&) (inRange <$> getBounds maze <*> pure (trace("in=" ++ show loc) loc)) (((==) '#') <$> readArray maze (trace("rd=" ++ show loc) loc))
こういうdebug文を挿入。
ghci> main rd=(1,3) in=(1,3) rd=(1,1) in=(1,1) rd=(-1,3) *** Exception: Error in array index
こういう事で、足を掬われるんですなあ。ならば、inRangeを後ろに配置して みるか。が、、、駄目だった。コンパイラが勝手に実行順番を入れかえちゃっ たぞ。こういう微妙なコードは禁止って事なのか。
repair it
原因が判明すれば、対処は易しい事だ。下記の様に修正した。
dig :: Maze -> (Int, Int) -> IO () dig maze (row, col) = do writeArray maze (row, col) ' ' directions <- shuffle [(0, -1), (-1, 0), (0, 1), (1, 0)] forM_ directions $ \(r, c) -> do let loc = (row + r * 2, col + c * 2) okP <- liftM (&& True) (inRange <$> getBounds maze <*> pure loc) when okP $ do kw <- readArray maze loc when ((==) kw '#') $ do writeArray maze (r + row, c + col) ' ' dig maze loc
そして、実行例。入口と出口が明確に示されていないけど、暗黙の了解事項と して、左上と右下が相当する。digの最後に、これらの壁を撤去するのが正式 だろうけど、それはNHKの解体キングダムにでも、任せておけば良い。
λ> main ##################### # # # # # # ### # ####### # # # # # # # # # ############### ### # # # # # # # ####### # # ### # # # # # # # ##### # # ### ##### # # # # # # # # ### # ##### ### # ### # # # # # # # # # ####### # ### # # # # # # # # ########### ##### # # # # # ################# # # # # # # # ### # # # ### ### # # # # # # #####################
エラーが一杯
なにくわぬ顔で結果をさっと提示したけど、苦難の道があったのよ。あえて、 失敗事例をあげておく。
まずは、liftM2 -> liftM への変更だ。
λ> :t liftM2 liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r λ> :t liftM liftM :: Monad m => (a1 -> r) -> m a1 -> m r
当初はliftM2が使われていた。そう (&&) で、評価したいから。それが、一つ になる場合はどうする家康。
すごいH本の344ページに答が出てた。通読は役にたたない。実例に遭遇して、 意味が分ってくるものなのね。
λ> liftM (+5) (Just 3) Just 8
ならば、第一引数は、(&& True) でいいな。壁かどうかの判定は、常にYesっ て事にしちゃえばいいんだ。これに思い至るまで、随分と余計な事をして、そ のエラーの撲滅で無駄をしてた。無知なんだから我流は、いかんぜよ。
次は、あられもない失敗。最初、こう記述しちゃったんだ。
when (==) kw '#' $ do
その結果、とんでもないエラーに見舞われた。
app/Main.hs:19:11: error: • Couldn't match type ‘()’ with ‘IO () -> IO ()’ Expected: Char -> IO () -> IO () Actual: Char -> () • In the result of a function call In the first argument of ‘($)’, namely ‘when (==) kw '#'’ In a stmt of a 'do' block: when (==) kw '#' $ do writeArray maze (r + row, c + col) ' ' dig maze loc | 19 | when (==) kw '#' $ do | ^^^^^^^^^^^^^^^^^ app/Main.hs:19:16: error: • Couldn't match expected type ‘Bool’ with actual type ‘a0 -> a0 -> Bool’ • Probable cause: ‘(==)’ is applied to too few arguments In the first argument of ‘when’, namely ‘(==)’ In the first argument of ‘($)’, namely ‘when (==) kw '#'’ In a stmt of a 'do' block: when (==) kw '#' $ do writeArray maze (r + row, c + col) ' ' dig maze loc | 19 | when (==) kw '#' $ do | ^^^^ app/Main.hs:19:21: error: • Couldn't match expected type ‘Char -> ()’ with actual type ‘Char’ • In the second argument of ‘when’, namely ‘kw’ In the first argument of ‘($)’, namely ‘when (==) kw '#'’ In a stmt of a 'do' block: when (==) kw '#' $ do writeArray maze (r + row, c + col) ' ' dig maze loc | 19 | when (==) kw '#' $ do | ^^
通常は、最初のエラーに注目して、それを潰す努力をするものだけど、例によっ て意味不明。ええ、色々とあがきましたよ。でエラーがエラーを呼ぶという悪 循環。
相当草臥れて、ボーとコーヒーを飲みながら、別のエラーを眺めていたんだ。 そしたら、
• Probable cause: ‘(==)’ is applied to too few arguments
これが目に止まった。これは関数なのに、引数が足りない。ピーンと気付いた ぞ。関数一式を括弧でくくれ。whenの侵略を許すな。
こうして、3件のエラーは、スーと消えたよ。エラーは、頼もしい助言者。邪 険にするな。少くとも、各エラーの冒頭から数行には、目を配ってあげよう。 ヒント満載だぞ。
thinking
今回の見せ場は、評価の順番に尽きる。記述した通りに実行される保証はないっ て事だ。
(and exp1 exp2) ;; for scheme (&&) exp1 exp2 -- for haskell
とやれば、schemeなら、exp1を評価した時点で結果がfalseなら、次のexp2は 評価されない。だって、S式全体でfalseって事が確定してるから。いわゆる ショートカットって奴だ。これで、多少の無駄(exp2を評価する事)が無くなる。 大事なテクニックとして、多用されてるぞ。
これに対してhaskellは、上で見たように、schemerとかの常識を逸脱してる。 内部的には、ショートカットが(多分)利用されてるだろうけど、評価の順番は haskellのコンパイラー次第って事だ。まあ、どちらの式の方が計算コストが 低いかとか吟味するんだろうけど。。
評価の順番で思い出したのは、他にもある。そう、letだ。 scheme: 変数束縛 に説明が有るように、letとlet*の違いね。letの場合は評価の順番は保証せず。 let*は、記述した順番通りに評価していく。
じゃhaskellは? 遅延評価だけだと出力の順番が定まらない例 上手い例だなあ。
Haskell の並列処理と遅延評価の罠 こういう楽しい記事も発見したぞ。 このサイト昔は良く見ていたなあ。
let、whereはなぜ必要か?【Haskellにおける局所的な変数】 関連でこういう も出てきた。
others
bingに指示した壁延し法により迷路作成。インチキっぽいな。適当に寄せ集め したんではなかろうか。落ちるしね。
import Data.Array.IO import Control.Monad import System.Random type Maze = IOArray (Int, Int) Int makeMaze :: Int -> Int -> IO Maze makeMaze w h = do maze <- newArray ((0, 0), (w-1, h-1)) 1 let start = (1, 1) dig maze start return maze dig :: Maze -> (Int, Int) -> IO () dig maze pos@(x, y) = do writeArray maze pos 0 let neighbors = [(x-2, y), (x+2, y), (x, y-2), (x, y+2)] validNeighbors <- filterM (\n -> do val <- readArray maze n return $ val == 1) neighbors case validNeighbors of [] -> return () ns -> do next <- choose ns let mid = ((fst pos + fst next) `div` 2, (snd pos + snd next) `div` 2) writeArray maze mid 0 dig maze next dig maze pos choose :: [a] -> IO a choose xs = do i <- randomRIO (0, length xs - 1) return $ xs !! i printMaze :: Maze -> IO () printMaze maze = do (_, (maxX, maxY)) <- getBounds maze forM_ [0..maxY] $ \y -> do forM_ [0..maxX] $ \x -> do cell <- readArray maze (x, y) putStr $ if cell == 1 then "#" else " " putStrLn "" main :: IO () main = do out <- makeMaze 11 11 -- must be odd printMaze out
ならば、少し復習。
こちらはhaskellでAIですってさ。
etc
こういうのを博物館と言うんだろうな。色々な言語での迷路作成。 色々な言語でハロワじゃ子供騙しだからね。せめてこれぐらいは、やってよね、 です。
そして、こちらは、言語コンバータです。裏でChatGPTが動いているに違いな い。
Welcome to Code Language Converter!