我正在阅读一个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
参数,这会打败monad的整个点吗?
非常感谢任何帮助/想法,谢谢。
答案 0 :(得分:7)
如果您发现普通的旧列表操作对您来说更自然,那么在考虑此代码时,从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
。正如您可能已经知道的那样,它与>>=
monad中的List
同义,但现在将其视为将KnightPos -> [KnightPos]
类型的函数映射到列表上可能更有帮助{ {1}}生成列表[KnightPos]
,然后将结果展平为单个列表。
好的,现在我们暂时放弃了monad,让我们回顾一下这个难题......让我们说你的初始[[KnightPos]]
是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]
的新函数替换KnightPos -> [KnightPos]
类型的函数 - 几乎相同的形状。因此,使用任何感觉自然的方式填写每个函数的实现,然后一旦它工作,返回monadic样式,用Sequence -> [Sequence]
替换concatMap
,依此类推。
答案 1 :(得分:3)
我建议让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个monadic语句中返回从start
到end
的所有可能路径。
扰流警报
pathIn3 :: KnightPos - &gt; KnightPos - &gt; [[KnightPos]]
pathIn3 start end = do
p&lt; - in3 start
守卫(p ==结束)
return $ getHistory p
答案 2 :(得分:2)
“这可能会让moveKnight有一个[KnightPos]
参数而不是KnightPos
参数...”
不,你不想这样做。保持功能尽可能基本。
“......哪会打败monad的整个点?”
好吧,如果您想要所有订单的移动,那么您不会在每次移动中使用join
中隐含的>>=
。您可以定义一个函数,从给定的起始点返回长度为 n 的所有路径的列表,我们可以将这些路径实现为传递的位置列表,出于效率原因,它们的顺序相反。
waysFrom :: KnightPos -> Int -> [[KnightPos]]
首先进行一次移动(或零)
waysFrom start 0 = [[start]]
waysFrom start 1 = map (\t -> [t, start]) $ moveKnight start
然后对于任意数量的移动,此时您可以再次使用>>=
来加入类似于trie的结构,这是由于直接递归到移动列表列表所致:
waysFrom start n = waysFrom start (n-1) >>= (\w -> [t:w | t<-moveKnight$head w])
可能有一种更好的方法可以做到这一点,但它实际上并没有“打败”monad的整个点。
答案 3 :(得分:1)
我正在阅读教程,我的解决方案是更改功能in3
和canReachIn3
in3 :: KnightPos -> [[KnightPos]]
in3 start = do
first <- moveKnight start
second <- moveKnight first
third <- moveKnight second
return [start, first, second, third]
canReachIn3 :: KnightPos -> KnightPos -> [[KnightPos]]
canReachIn3 start end = filter (elem end) $ in3 start
in3
的结果包含列出所有可能的路径。