Scala中如何停止回溯?

发布于 2024-12-27 16:46:13 字数 145 浏览 0 评论 0 原文

假设我正在使用回溯来解决问题(例如 N-Queen)。如果我想找到唯一一个(第一个)解决方案而不是所有解决方案怎么办?

我想我可以强制(例如使用可变的布尔标志)。我想知道如何才能功能性地做到这一点

Suppose I am solving a problem (e.g. N-Queen) with backtracking. What if I want to find the only one (the 1-st) solution rather than all of them.

I guess I can do it imperatively (e.g. with a mutable boolean flag). I wonder how I can do it functionally.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心的位置 2025-01-03 16:46:13

虽然 Scala 的 Option 可以在这里工作,正如其他两个答案所指出的那样,更惯用的功能方法是使用“惰性列表” - 或 Stream,在 Scala 中 — 表示解决方案集。

我发现自己编写了这样的代码,例如:

trait Node[A] {
  def children: Stream[A with Node[A]]

  def dfs(f: A => Boolean): Stream[A] = this.children.flatMap {
    child => if (f(child)) Stream(child) else child.dfs(f)
  }
}

现在假设我有一个扩展 Node[Board] 的 Board 类,并且它具有 的实现Children 方法返回所有有效的棋盘以及一个附加棋子。假设它还有一些其他有用的东西,例如 size 方法、带有 empty 的伴生对象等。

然后我可以编写以下代码来获取 Stream解决方案的

val solutions = Board.empty.dfs(_.size == 8)

Stream 是惰性的,仅评估其头部,因此现在我们仅在树中搜索足够远的距离来找到第一个解决方案。我们可以使用 head 得到这个解决方案:

scala> solutions.head
res1: Board = 
o . . . . . . .
. . . . o . . .
. . . . . . . o
. . . . . o . .
. . o . . . . .
. . . . . . o .
. o . . . . . .
. . . o . . . .

或者其他。但如果我想要的话,我也可以获得其他结果:

scala> solutions(10)
res2: Board = 
. o . . . . . .
. . . . . . o .
. . . . o . . .
. . . . . . . o
o . . . . . . .
. . . o . . . .
. . . . . o . .
. . o . . . . .

这将搜索足够多的树来找到第十个解决方案,然后停止。

Option 方法相比,Stream 的一大优点是,如果我需要的话,我可以获得额外的结果,而无需为第一个结果支付更多费用。

While Scala's Option will work here, as two other answers have pointed out, a more idiomatic functional approach would be to use a "lazy list"—or a Stream, in Scala—to represent the set of solutions.

I've found myself writing code like this, for example:

trait Node[A] {
  def children: Stream[A with Node[A]]

  def dfs(f: A => Boolean): Stream[A] = this.children.flatMap {
    child => if (f(child)) Stream(child) else child.dfs(f)
  }
}

Now suppose I have a Board class that extends Node[Board] and that has an implementation of the children method that returns all valid boards with one additional piece. Suppose it also has some other useful stuff, like a size method, a companion object with an empty, etc.

I can then write the following to get a Stream of solutions:

val solutions = Board.empty.dfs(_.size == 8)

A Stream is lazy and only evaluates its head, so right now we've only searched the tree far enough to find the first solution. We can get this solution using head:

scala> solutions.head
res1: Board = 
o . . . . . . .
. . . . o . . .
. . . . . . . o
. . . . . o . .
. . o . . . . .
. . . . . . o .
. o . . . . . .
. . . o . . . .

Or whatever. But I can also get other results if I want them:

scala> solutions(10)
res2: Board = 
. o . . . . . .
. . . . . . o .
. . . . o . . .
. . . . . . . o
o . . . . . . .
. . . o . . . .
. . . . . o . .
. . o . . . . .

This searches just enough more of the tree to find the tenth solution, and then stops.

The big advantage of Stream over the Option approach is that I can get additional results if I need them, without paying more for the first.

标点 2025-01-03 16:46:13

这是一个简单的案例,深度优先搜索在找到要查找的内容时停止。正如 Chris K 的回答中提到的,它使用了 Option

case class Tree[A](v: A, subtrees: Tree[A]*) {
  def dfs(s: A): Option[A] = {
    println("visiting " + v)
    subtrees.foldLeft(if(v == s) Some(v) else None)((r, t) => 
      if(r.isDefined) 
        r 
      else 
        t.dfs(s)
    )
  }
  override def toString() = "Tree(%s%s%s)".format(v, if(subtrees.nonEmpty) ", " else "", subtrees.mkString(", "))
}

用法:

scala> val t = Tree(1, Tree(2, Tree(3), Tree(4)), Tree(5, Tree(6), Tree(7)))
t: Tree[Int] = Tree(1, Tree(2, Tree(3), Tree(4)), Tree(5, Tree(6), Tree(7)))

t 看起来像

     1
   /   \
  2     5
 / \   / \
3   4 6   7    

因此我们可以搜索一个元素并跟踪它访问的节点:

scala> t.dfs(6)
visiting 1
visiting 2
visiting 3
visiting 4
visiting 5
visiting 6
res42: Option[Int] = Some(6)

请注意,在找到我们要查找的内容后,我们不再访问任何节点。

Here's a simple case with a depth-first search that stops when it finds what it's looking for. It makes use of Option, as mentioned in Chris K's answer.

case class Tree[A](v: A, subtrees: Tree[A]*) {
  def dfs(s: A): Option[A] = {
    println("visiting " + v)
    subtrees.foldLeft(if(v == s) Some(v) else None)((r, t) => 
      if(r.isDefined) 
        r 
      else 
        t.dfs(s)
    )
  }
  override def toString() = "Tree(%s%s%s)".format(v, if(subtrees.nonEmpty) ", " else "", subtrees.mkString(", "))
}

Usage:

scala> val t = Tree(1, Tree(2, Tree(3), Tree(4)), Tree(5, Tree(6), Tree(7)))
t: Tree[Int] = Tree(1, Tree(2, Tree(3), Tree(4)), Tree(5, Tree(6), Tree(7)))

The tree t looks like

     1
   /   \
  2     5
 / \   / \
3   4 6   7    

So we can search for an element and trace the nodes it visits:

scala> t.dfs(6)
visiting 1
visiting 2
visiting 3
visiting 4
visiting 5
visiting 6
res42: Option[Int] = Some(6)

Notice that we don't visit any more nodes after we find what we're looking for.

病女 2025-01-03 16:46:13

假设您正在使用递归搜索函数,您的函数应该返回结果(即皇后的位置)或指示找不到该分支的结果。 Scala 可能有一个选项/也许类型,你可以使用它。这个建议同样适用于任何函数式语言。

Assuming you're using a recursive search function, your function should either return a result (i.e. positioning of the queens) or an indication that no result could be found for that branch. Scala probably has an option/maybe type, you can use that. This advice is equally applicable to any functional language.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文