这里是用于计算骑士是否可以移动到x移动中所需位置的片段:
import Control.Monad (guard)
import Control.Monad.Writer
type KnightPos = (Int,Int)
-- function returning array of all possible kinght moves from desired position
moveKnight :: KnightPos -> [KnightPos]
moveKnight (c,r) = do
(c',r') <- [ (c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)
]
guard (c' `elem` [1..8] && r' `elem` [1..8])
return (c',r')
-- nice little function tells us
-- whether knight can move to desired position within x moves
reaches :: KnightPos -> KnightPos -> Int -> Bool
reaches _ _ 0 = False
reaches from pos n =
any (\p -> p == pos || reaches p pos (n-1)) $ moveKnight from
-- the result is True or False
-- does knight can move from cell 6,2 to cell 6,3 within 3 moves
main = print $ reachesm (6,2) (6,1) 3
现在我想将Writer monad添加到'reach'功能,但完全丢失了 我来的是,
-- not so nice and little yet
reachesm :: KnightPos -> KnightPos -> Int -> Writer [String] [Bool]
reachesm _ _ 0 = return [False]
reachesm from pos n = do
tell [ "-->" ++ (show pos) ]
p <- moveKnight from -- ???
np <- reachesm p pos (n-1)
return(p == pos || any np)
但它甚至没有编译。我怀疑它的时间是monad transormers吗?
UPD: 所以,最后我们来重写,但我仍然不满意,因为 reachm与纯变体的运行方式不同,它可以深度计算所有 n 步骤,但是 我希望它一旦找到答案就停止迭代。 这样修改难吗? 另一个问题是关于懒惰,似乎在做块计算不是懒惰是真的吗?
reachesm :: KnightPos -> KnightPos -> Int -> Writer [String] Bool
reachesm _ _ 0 = return False
reachesm from pos n = do
tell [ "-->" ++ (show from) ]
let moves = moveKnight from
np <- forM moves (\p -> reachesm p pos (n-1))
return (any (pos ==) moves || or np)
答案 0 :(得分:4)
好的,我们的目标是将此功能放入Wrtier monad。
reaches :: KnightPos -> KnightPos -> Int -> Bool
reaches _ _ 0 = False
reaches from pos n =
any (\p -> p == pos || reaches p pos (n-1)) $ moveKnight from
所以,让我们从类型签名开始。只需在结果类型周围添加Writer
:
reaches :: KnightPos -> KnightPos -> Int -> Writer [String] Bool
原始函数未返回[Bool]
,因此新函数没有理由返回Writer [String] [Bool]
。提升基础案例的返回值:
reaches _ _ 0 = return False
正如您所怀疑的那样,处理递归案例会变得有点棘手。让我们开始时就像tell
当前pos
所做的那样,你做得对。
reaches from pos n = do
tell ["-->" ++ show pos]
moveKnight
不在编写器monad中,因此我们不必使用<-
来绑定它。只需使用let
(即如果我们需要,我们可以在使用新变量时替换moveKnight pos
):
let moves = moveKnight from
现在让我们获取递归结果列表。这次我们做必须绑定,因为我们从Bool
中获取Writer [String] Bool
。我们将使用map
,mapM :: (a -> m b) -> [a] -> m [b]
:
np <- mapM (\p -> reachesm p pos (n-1)) ps
现在np :: [Bool]
。那么我们就完成了你的逻辑:
return (any (pos ==) moves || or np)
or :: [Bool] -> Bool
只是any id
。
请记住,要绑定变量,当您想从a
获取m a
时,请使用<-
,否则请使用let
。
要从main
使用它,您可以使用runWriter :: Writer w a -> (w,a)
:
main = print $ runWriter (reachesm (6,2) (6,1) 3)
此代码仍然有错误,但它会编译并产生您通过编写器通道告诉它的内容,因此您应该可以轻松调试剩余的问题。希望这有帮助。
答案 1 :(得分:4)
听起来你真的致力于使用编写器monad。所以这是一个解决方案:
reachesm :: KnightPos -> KnightPos -> Int -> [Writer [String] Bool]
reachesm from pos n | from == pos = return (return True)
reachesm _ _ 0 = return (return False)
reachesm from pos n = do
p <- moveKnight from
map (tell [show from ++ "-->" ++ show p] >>) $ reachesm p pos (n-1)
main = print . filter fst . map runWriter $ reachesm (6,2) (6,3) 3
但这很愚蠢。编写器monad仅用作列表的巴洛克式界面。 Writer
不是你问题的解决方案,尽管你明确希望它有多少。以下是我编写此算法的方法:
-- returns all paths of length at most n to get to target
paths :: Int -> KnightPos -> KnightPos -> [[KnightPos]]
paths 0 _ _ = []
paths n target p
| p == target = return [p]
| otherwise = map (p:) . paths (n-1) target =<< moveKnight p
main = print $ paths 4 (6,3) (6,2)
没有作家monad,只是友好的旧(:)
运营商。
答案 2 :(得分:3)
这是一个有效的版本:
main = print $ runWriterT (reachesm (6,2) (6,5) 4)
reachesm :: KnightPos -> KnightPos -> Int -> WriterT [String] [] Bool
reachesm _ _ (-1) = return False
reachesm from pos n
| from == pos = tell [ "-->" ++ (show from) ] >> return True
| otherwise =
do
p <- lift (moveKnight from)
t <- reachesm p pos (n-1)
guard t
tell [ "-->" ++ (show from) ]
return True
您的moveKnight
功能也可以这样写:
moveKnight :: KnightPos -> [KnightPos]
moveKnight (c,r) = filter legal possible
where possible = [ (c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)]
legal (c',r') = (c' `elem` [1..8] && r' `elem` [1..8])
答案 3 :(得分:2)
对于在树中寻找路径而言,(至少对我来说)更容易一点。
首先我们从Data.Tree
导入一些函数:
import Data.Tree (levels, unfoldTree)
现在我们编写一个用于展开具有历史记录的树的函数,获取树的顶部n + 1
级别,并查看它们是否包含所需的位置:
reaches :: KnightPos -> KnightPos -> Int -> Maybe [KnightPos]
reaches from pos n = lookup pos . concat . take (n + 1) $ levels tree
where
tree = unfoldTree unfolder (from, [])
unfolder (p, hist) = ((p, hist'), map (flip (,) hist') $ moveKnight p)
where hist' = p : hist
这给了我们在给定步数中从结束位置到开头的路径(如果存在):
*Main> reaches (6,2) (6,1) 3
Just [(6,1),(7,3),(8,1),(6,2)]
(如果我们想要一条从头到尾的路径,我们当然可以改变它。)
这是一个快速的解决方案,它不一定非常有效,但我发现它在概念上很简单。
答案 4 :(得分:0)
这是我最近的尝试:
import Control.Monad
type KnightPos = (Int,Int)
moveKnight :: KnightPos -> [KnightPos]
moveKnight (c,r) = do
(c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)]
guard (c' `elem` [1..8] && r' `elem` [1..8])
return (c',r')
findpath :: KnightPos -> KnightPos -> Int -> [[KnightPos]]
findpath start end steps = trail [start] steps
where trail curtrail steps = do
nextstep <- moveKnight $ last curtrail
if steps == 1 then
do guard (nextstep == end)
return (curtrail ++ [nextstep])
else trail (curtrail ++ [nextstep]) (steps - 1)