在Haskell中,制作2D数组有许多方法,以下是一个相对繁琐的示例,将字符读入Data.Array数组,然后使用所谓的 state monad
移动元素:
import Data.Array
import Control.Monad.State.Strict
main = do str <- getContents
let array = mkThingArray str
limits = snd (bounds array)
initialState = ((0::Int,-1::Int),limits,array)
((position,(h,w),a)) <- execStateT findpath initialState
let chars = elems $ fmap toChar a
putStrLn ""
putStrLn $ splitText (w+1) chars
parseArray str = listArray ((0,0),(height-1, width-1)) total where
rawlines = lines str
ls = filter (not . null) rawlines
lens = map length ls
height = length ls
width = minimum lens
proper = map (take width) ls
total = concat proper
data Thing = Open | Closed | Home | Taken deriving (Show, Eq, Ord)
toThing c = case c of '-' -> Open; '#' -> Closed; '@' -> Home;
'+' -> Taken; _ -> error "No such Thing"
toChar c = case c of Open -> '-'; Closed -> '#';
Home -> '@'; Taken -> '+'
mkThingArray str = fmap toThing (parseArray str)
接着讲述一种非常原始的状态变化“逻辑”:
findpath = moveright
where
moveright = do ((n,m), (bound1,bound2), arr) <- get
if m < bound2
then case arr ! (n,m+1) of
Open -> do liftIO (putStrLn "moved right")
put ((n,m+1), (bound1,bound2), arr // [((n,m+1),Taken)])
moveright
Closed -> movedown
Home -> return ()
Taken -> movedown
else movedown
movedown = do ((n,m), (bound1,bound2), arr) <- get
if n < bound1
then case arr ! (n+1,m) of
Open -> do liftIO (putStrLn "moved down")
put ((n+1,m), (bound1,bound2), arr // [((n+1,m),Taken)])
moveright
Closed -> moveright
Home -> return ()
Taken -> moveright
else moveright
splitText n str = unlines $ split n [] str
where split n xss [] = xss
split n xss str = let (a,b) = splitAt n str
in if not (null a)
then split n (xss ++ [a]) b
else xss
在这种情况下,输出结果将会像这样。
逻辑需要更加复杂,包括moveleft和moveup等等,但这只是为了给出一个思路或者想法。
编辑:这里有一个版本,不使用中间类型,并且不将任何IO引入状态机。这应该更易于在ghci中使用,因此您可以更轻松地分析它。
import Data.Array
import Control.Monad.Trans.State.Strict
main = do str <- readFile "input.txt"
((pos,(h,w),endarray)) <- execStateT findpath
(mkInitialState str)
putStrLn $ prettyArray endarray
type Pos = (Int, Int)
type Arr = Array Pos Char
type ArrState = (Pos, Pos, Arr)
parseArray :: String -> Arr
parseArray str = listArray ((1,1),(height, width)) (concat cropped) where
ls = filter (not . null) (lines str)
width = minimum (map length ls)
height = length ls
cropped = map (take width) ls
prettyArray :: Arr -> String
prettyArray arr = split [] (elems arr)
where (ab,(h,w)) = bounds arr
split xss [] = unlines xss
split xss str = let (a,b) = splitAt w str
in if null a then unlines xss else split (xss ++ [a]) b
mkInitialState :: String -> ArrState
mkInitialState str = ((1::Int,0::Int), limits, array)
where array = parseArray str
limits = snd (bounds array)
makeStep :: Arr -> Pos -> Arr
makeStep arr (n, m) = arr // [((n,m),'+')]
moveRight, moveDown, findpath :: Monad m => StateT ArrState m ()
moveRight = do ((n,m),bounds,arr) <- get
put ((n,m+1), bounds, makeStep arr (n,m+1))
moveDown = do ((n,m),bounds,arr) <- get
put ((n+1,m), bounds, makeStep arr (n+1,m))
findpath = tryRight
where
tryRight = do ((n,m), (_,bound2), arr) <- get
if m < bound2
then case arr ! (n,m+1) of
'@' -> return ()
'-' -> do moveRight
tryRight
_ -> tryDown
else tryDown
tryDown = do ((n,m), (bound1,_), arr) <- get
if n < bound1
then case arr ! (n+1,m) of
'@' -> return ()
'-' -> do moveDown
tryRight
_ -> tryRight
else tryRight
runInput :: String -> String
runInput str = prettyArray endarray
where ((position,(h,w),endarray)) = execState findpath (mkInitialState str)
test :: String -> IO ()
test str = putStrLn (runInput str)
t1 = unlines ["---#--###----"
, ""
, "-#---#----##-"
, ""
, "------------@"
] :: String
t2 = unlines ["---#--###----"
,""
,"---#-#----##-"
,""
,"------------@"
] :: String