【问题标题】:How can I improve this breadth-first search in Scala to make it more idomatic FP?如何改进 Scala 中的这种广度优先搜索,使其更加符合 FP?
【发布时间】:2019-08-25 02:27:37
【问题描述】:

一直在寻找一种无需队列的方法,并使其尾递归。我认为 LazyLists 也可能有所帮助。队列会更快吗?我基本上是通过每个函数调用与下一级子级发送突变状态。

case class Tree [A] (
                      value : A,
                      Right: Option[Tree[A]],
                      Left: Option[Tree[A]]
                    )
object Tree {
  def liftChildren[A](t: Tree[A]) = {
    List(t.Left, t.Right).flatten
  }

  def findChild[A](value: A, t: Tree[A]) : Option[Tree[A]] = {
    var lvl = 0
      def searchChildren(t: List[Tree[A]]): (Option[Tree[A]], List[Tree[A]]) = {
        // could be removed, just for fun
        lvl += 1
        t.foreach(tt => println(s"Scanning Level ${lvl.toString} Value ${tt.value.toString}"))
        //
        val curfind = t.find(tt => {
          tt.value == value
        })
        curfind match {
          case Some(tr) => (Some(tr), t)
          case None => {
            val children: List[Tree[A]] = t.flatMap(tt => Tree.liftChildren(tt))
            children.isEmpty match {
              case true => (None, List.empty)
              case false => searchChildren(children)
            }
          }
        }
      }
    searchChildren(List(t))._1
    }
}

object main extends App {
  println("hello world")
  val tree = Tree[Int](
    1,
    Some(
      Tree[Int](2, None, Some(
        Tree[Int](5,None, Some(Tree[Int](6, None,None))))
      )
    ) ,
    Some(
      Tree[Int](
        3,
        Some(
          Tree[Int](4, None, Some(Tree[Int](7, None,None)))
        ), None
      )
    )
  )
  val res = Tree.findChild(6, tree)
  println("FoundIt" + res)
}

它按我的预期工作。我只是想知道这是否会是更好或更惯用的 FP。猫图书馆会有所帮助吗?

【问题讨论】:

  • 对我来说看起来不错。除了用于记录的 var lvl 之外,其他一切都是不可变的。据我所知,这是很好的功能代码。
  • 我在liftChildren中使用lift这个词是否正确?这通常被称为电梯吗?
  • 不,我不认为这就是 fp 人通常所说的提升。提升通常是指将一个类型移动到一些新的上下文中,通常是某种单子。但是“liftChildren”是不言自明的。当您在这里使用电梯时,人们会明白您的意思。
  • 我认为让您的电梯功能足够灵活以适应树以外的类型是不值得的。它会降低您从未使用过的功能的可读性。我认为实现您想要的方法是使用更高种类的类型。您可能想为它创建另一个问题。 Luis 已经很好地回答了您现有的问题。
  • 实际上,为了使您的 lift 函数可泛化到不同的类型,您需要提供一个参数,该参数是一个告诉 lift 如何将子项插入到 List 上下文中的函数。对我来说似乎没有必要。只是我的 2 美分。

标签: scala functional-programming breadth-first-search


【解决方案1】:

这是一个尾递归实现,使用模式匹配

final case class Tree[+A](value: A, left: Option[Tree[A]], right: Option[Tree[A]])

def find[A](value: A)(tree: Tree[A]): Option[Tree[A]] = {
  import scala.collection.immutable.Queue

  @annotation.tailrec
  def bfs(queue: Queue[Tree[A]]): Option[Tree[A]] =
    queue.dequeueOption match {
      case None => None

      case Some((tree, remaining)) => tree match {
        case Tree(`value`, _, _) => Some(tree)
        case Tree(_, Some(left), Some(right)) => bfs(queue = remaining.enqueue(left).enqueue(right))
        case Tree(_, Some(left), None) => bfs(queue = remaining.enqueue(left))
        case Tree(_, None, Some(right)) => bfs(queue = remaining.enqueue(right))
        case Tree(_, None, None) => bfs(queue = remaining)
      }
    }

  bfs(queue = Queue(tree))
}

def find[A](value: A)(tree: Tree[A]): Option[Tree[A]] = {
  @annotation.tailrec
  def dfs(stack: List[Tree[A]]): Option[Tree[A]] =
    stack match {
      case Nil => None

      case tree :: remaining => tree match {
        case Tree(`value`, _, _) => Some(tree)
        case Tree(_, Some(left), Some(right)) => dfs(stack = left :: right :: remaining)
        case Tree(_, Some(left), None) => dfs(stack = left :: remaining)
        case Tree(_, None, Some(right)) => dfs(stack = right :: remaining)
        case Tree(_, None, None) => dfs(stack = remaining)
      }
    }

  dfs(stack = List(tree))
}

以下是一些使用 LazyList 的实现。

final case class Tree[+A](value: A, children: List[Tree[A]])

// DFS by right.
def find[A](value: A)(tree: Tree[A]): Option[Tree[A]] =
  LazyList.unfold(List(tree)) {
    case Nil => None
     case tree :: remaining => Some((tree, tree.children reverse_::: remaining))
  }.find(tree => tree.value == value)

// DFS by left.
def find[A](value: A)(tree: Tree[A]): Option[Tree[A]] =
  LazyList.unfold(List(tree)) {
    case Nil => None
    case tree :: remaining => Some((tree, tree.children ::: remaining))
  }.find(tree => tree.value == value)

// BFS
def find[A](value: A)(tree: Tree[A]): Option[Tree[A]] =
  LazyList.unfold(Queue(tree)) { queue =>
    queue.dequeueOption.map {
      case (tree, remaining) => (tree, remaining.enqueueAll(tree.children))
    }
  }.find(tree => tree.value == value)

【讨论】:

  • 感谢您分享您的代码。它帮助我复习了 dfs 和 bfs。确实,操作的代码已经是尾递归的,但他或她确实询问过使用队列来实现 bfs。再次感谢。
  • 为什么树案例类上的 A+?感谢您显示排队选项。我的实现将孩子转换为 Tree[A] 的列表,理论上它可以适用于超过左右形状的树。它还使模式匹配变得不那么复杂。想知道惰性列表实现是否会“更好”。
  • @SMorrigan +A 是为了制作 covariant。如果您在编辑中看到,第一个 bfs 实现做了类似的事情,我只是想保持简单(恕我直言,对于两个孩子,模式匹配似乎更简单)。我不确定 LazyList 是什么意思,你会在哪里使用它以及为什么?
  • val children: List[Tree[A]] = t.flatMap(tt => Tree.liftChildren(tt)) 在这里它正在创建当前级别的所有子级的列表,理论上如果这是一个 LazyList,它会在扫描大行时使用更少的内存吗?我同意两个孩子的模式匹配更容易阅读。
  • @SMorrigan t.flatMap 是如何工作的? t 不是 Tree,您没有展示它如何具有 flatMap 方法。无论如何,也许 LazyList 可以工作,如果你的树超级大,但我仍然不清楚它如何在这个算法中使用。
猜你喜欢
  • 2017-05-11
  • 1970-01-01
  • 2010-12-17
  • 1970-01-01
  • 2011-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多