如何将列表单子函数转换为广度优先搜索?

Posted

技术标签:

【中文标题】如何将列表单子函数转换为广度优先搜索?【英文标题】:How do I convert a list monadic function to a breadth-first search? 【发布时间】:2014-03-11 18:33:56 【问题描述】:

我刚刚克服了弄清楚如何使用 List monad 进行非确定性计算的难题。但是我相信我的算法会受益于广度优先搜索,而不是从 List monad 获得的深度优先搜索。

这是我算法中有趣部分的摘录。它是逻辑谜题 Akari 的求解器。

solve :: Game -> [Game]
solve game = do
        let ruleBasedSolverResult = applyRuleBasedSolversUntilSteady game
        guard $ consistant ruleBasedSolverResult
        if solved ruleBasedSolverResult
                then return ruleBasedSolverResult
                else speculate ruleBasedSolverResult

speculate :: Game -> [Game]
speculate game = do
        coord <- coords game
        guard $ lightableUnlit game coord
        let solutions = solve $ light game coord
        if null solutions
                then solve $ exclude game coord
                else solutions

基本上,它会应用一些基本的确定性规则来查看是否可以解决问题。如果不是,它会尝试在不同的地方放灯。如果灯光在递归解决后使谜题不一致,它会在灯光所在和继续进行的位置放置一个排除标记。如果它在放置灯光时找到解决方案,则将其添加到解决方案列表中。

这很好用,但速度很慢,因为通常有一个明显的选择来推测哪个坐标,这会很快导致不一致的谜题,并让您放置一个只有一(或两)级搜索的 x,但如果它直到搜索到一半才选择那个坐标,然后它首先咀嚼了一堆无趣的东西。因此,广度优先搜索的想法。

我在谷歌上搜索了“广度优先非确定性单子”之类的东西,得到了一些我难以理解的结果,例如:

Control.Monad.Omega 这对我的需要来说似乎有点矫枉过正,因为它似乎可以防止无限发散的确定性,这对我来说并非如此,而且我并不完全理解它。

Control.Monad.WeightedSearch Control.Monad.Omega 的文档建议在将其用作 Monad 时改用它,但我认为权重对于我的需求来说也有点矫枉过正。我可能只有 2 个权重,一个用于有解决方案的事物,一个用于没有解决方案的事物。

Control.Monad.Level我不相信这能满足我的要求,因为只有树叶才有价值。

Data.Tree我想这可能是我想要使用的,但我不确定如何将我的 List monadic 代码转换为使用它,尽管我觉得有一种很好的方式。

我的下一个问题是关于如何并行化它:)

【问题讨论】:

【参考方案1】:

我相信"Backtracking, Interleaving, and Terminating Monad Transformers" (Functional Pearl) by Kiselyov, Shan and Friedman 提供了一个解决方案。

免责声明:我不是这项工作的专家!

基本上,您必须使用不同的 monad。由于ListT monad 转换器执行深度优先,他们提出了一个新的 monad 转换器LogicT,它执行广度优先。 (如果您对 monad 转换器不感兴趣,您可以将转换器应用到 Id 以获取常规 monad)。

首先,他们认识到其他方法的不足:

MonadPlus 的大多数实现执行的直接深度优先搜索是不公平的:在两个备选方案之间的非确定性选择先尝试第一个备选方案的每个解决方案,然后再尝试第二个备选方案的任何解决方案。当第一个选项提供无限数量的解决方案时,第二个选项永远不会尝试,从而使搜索不完整。事实上,正如我们在第 3 节中的示例所示,公平回溯有助于更多的逻辑程序终止。

[...]

许多现有回溯 monad 的第二个缺陷是采用 Prolog 的切割,它将否定与修剪混淆。从理论上讲,求反和剪枝中的每一个都独立地使逻辑编程语言更具表现力

[...]

第三个实际缺陷是经常被遗忘的***接口:如何运行可能返回无限数量答案的计算并与之交互?最常见的解决方案是提供一个可以根据需要在顶层消费或处理的流。但是在 monad 转换器的情况下,这个解决方案只有在基本 monad 是非严格的(例如 Haskell 的惰性列表 monad 和 LazyST)时才有效。在基本 monad 很严格的情况下,即使我们只需要一个答案,通过强制对整个流进行求值,求值也可能会发生分歧。

然后他们提出了一个基于LogicT monad 转换器和msplit 函数的解决方案。虽然代码的链接坏了,但我在 Hoogle 上搜索了LogicT 并找到了this。

我希望阅读这篇论文能让你对这个主题有一个很好的背景,并帮助你理解如何使用你已经找到的项目。

如果您觉得这篇论文有用,别忘了查看它的参考文献和引用它的其他论文!

【讨论】:

感谢您的指点。我在搜索时也看到了那篇论文,但在我发现的许多相当密集的材料中,我不确定应该关注哪一个。我会更仔细地看一看。 @JakeBrownson 是的,阅读它肯定需要一些工作。抱歉,我没有更简洁的代码 sn-p。祝你好运! @JakeBrownson 我刚刚从 Hoogle 找到了代码 -- hackage.haskell.org/package/logict Here's 一篇解释了 Logic monad 更“轻量级”的论文,恕我直言。 好的,我阅读了很多不同的东西,我开始了解 logict 背后的想法。我已经在我的代码中交换了几个不同的 monad,C.M.Logic、C.M.Stream、C.M.Omega,但它们似乎仍然陷入对谜题一部分的深入搜索,而不是进行更浅的评估。我觉得他们的公平搜索不够公平,或者我没有正确使用 C.M.Stream.suspended 之类的东西。目前,我刚刚尝试将它洒在不同的地方,但并没有真正理解它。我会继续阅读:)。

以上是关于如何将列表单子函数转换为广度优先搜索?的主要内容,如果未能解决你的问题,请参考以下文章

BFS - 广度优先搜索 - 邻接列表表示法

在广度优先搜索中确定 n 个孩子的级别

如何理解分支定界中广度优先搜索的内存问题

尝试将广度优先搜索方法写入图形时出现分段错误

递归/回溯/深度优先搜索/广度优先搜索 /动态规划/二分搜索/贪婪算法

算法浅谈——走迷宫问题与广度优先搜索