Haskell Monad函数
我正在浏览一个Haskell教程,并且给出了一段代码,用于在国际象棋中移动骑士:
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')
in3 :: KnightPos -> [KnightPos]
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
canReachIn3 :: KnightPos -> KnightPos -> Bool
canReachIn3 start end = end `elem` in3 start
一个练习就是修改函数,以便canReachIn3
告诉你如果可以到达那里,你可以采取什么样的动作来达到终点。
本教程基本上没有练习,所以我遇到了这样的基本问题......我正在考虑将所有3个函数的返回值更改为[[KnightPos]],其中1个大列表包含每个可能的排序的列表移动。 这可能会涉及moveKnight有一个[KnightPos]
参数而不是KnightPos
,然后这会击败monads的全部点吗?
任何帮助/想法将不胜感激,谢谢。
如果您发现简单的旧列表操作对您来说更自然,那么在思考此代码时,它可能有助于退出Monad概念。 因此,您可以重写示例代码(对易读性进行一些清理),如下所示:
type KnightPos = (Int,Int)
moveKnight :: KnightPos -> [KnightPos]
moveKnight (c,r) = filter legal candidates where
candidates = [(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]
in3 :: KnightPos -> [KnightPos]
in3 start = concatMap moveKnight (concatMap moveKnight (moveKnight start))
canReachIn3 :: KnightPos -> KnightPos -> Bool
canReachIn3 start end = end `elem` in3 start
秘诀在于concatMap
。 正如你可能已经知道的那样,它与List
monad中的>>=
是同义词,但现在可能更有帮助,将它看作是将KnightPos -> [KnightPos]
类型的函数映射到列表[KnightPos]
以产生一个列表列表列表[[KnightPos]]
,然后将结果展平成单个列表。
好吧,现在我们已经放弃了monads,让我们回顾一下这个难题...让我们假设你的初始KnightPos
是(4,4)
,并且你想要跟踪从该位置开始的所有可能的移动序列。 因此定义另一种类型的同义词:
type Sequence = [KnightPos]
然后,您需要moveKnight
对这些序列进行操作,从序列中的最后一个位置找到所有可能的移动:
moveKnight :: Sequence -> [Sequence]
因此,从序列[(4,4)]
,我们会得到序列列表[[(4,4), (6,3)], [(4,4), (6,5)], [(4,4), (2,3)], ... ]
。 然后我认为你需要对in3
做出的唯一改变是相应地修正它的类型签名:
in3 :: Sequence -> [Sequence]
我不认为实际的实施变化。 最后,你会想让canReachIn3
看起来像这样:
canReachIn3 :: KnightPos -> KnightPos -> [Sequence]
我将故意实现的细节留在这里,因为我不想完全毁掉你的难题,但我希望我已经在这里阐明了关于列表没有特别“特殊”的一点,或列表的列表,或其他。 我们所做的只是用类型为Sequence -> [Sequence]
的新函数替换了KnightPos -> [KnightPos]
类型的KnightPos -> [KnightPos]
Sequence -> [Sequence]
- 形状几乎相同。 因此,使用任何风格感觉自然的方式来填充每个函数的实现,然后一旦你有它的工作,回到一元风格,用>>=
替换concatMap
等等。
我建议让KnightPos
成为一个数据结构,它不仅能够保存当前药水,还能够KnightPos
它来自的KnightPos
:
data KnightPos = KnightPos {history :: Maybe KnightPos, position :: (Int,Int) }
然后您需要实现Eq和Show类,并修复moveKnight
以便它返回此结构的适当历史记录集:
moveKnight :: KnightPos -> [KnightPos]
moveKnight p@KnightPos{position = (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 $ KnightPos (Just p) (c',r')
确保你理解这个函数的最后一行。
函数in3
现在可以不加修改地工作。 我编写了一个新函数, pathIn3 :: KnightPos -> KnightPos -> [[KinghtPos]]
,它返回3个一元语句中从start
到end
所有可能路径。
扰流警报
pathIn3 :: KnightPos - > KnightPos - > [[KnightPos]]
pathIn3 start end = do
p < - in3开始
警卫(p ==结束)
返回$ getHistory p
“这可能会涉及再有moveKnight [KnightPos]
而不是一个参数KnightPos
一个......”不,你不希望这样做。 尽可能保持基本功能。
“......然后这会击败monad的全部点吧?” 那么,如果你想要所有的移动顺序,你就不要在每一步都使用>>=
隐含的join
。 您可以定义一个函数,从给定起点返回长度为n的所有路径的列表,我们可以将这些路径作为传递的位置列表实现,以效率相反的顺序。
waysFrom :: KnightPos -> Int -> [[KnightPos]]
首先一个动作(或零)
waysFrom start 0 = [[start]]
waysFrom start 1 = map (t -> [t, start]) $ moveKnight start
然后进行任意数量的移动,此时您可以再次使用>>=
将直接递归产生的类似特里结构加入到移动列表列表中:
waysFrom start n = waysFrom start (n-1) >>= (w -> [t:w | t<-moveKnight$head w])
可能有更好的方法来做到这一点,但它不会真正“击败”monad的全部点。
链接地址: http://www.djcxy.com/p/43355.html