Как реализовать поиск в ширину поиск в Scala с ФП

мне интересно, как реализовать поиск в ширину в Scala, используя функциональное программирование.

вот мой первый, нечистый, код:

  def bfs[S](init: S, f: S => Seq[S], finalS: S => Boolean): Option[S] = {
    val queue = collection.mutable.Queue[S]()

    queue += init
    var found: Option[S] = None

    while (!queue.isEmpty && found.isEmpty) {
      val next = queue.dequeue()
      if (finalS(next)) {
        found = Some(next)
      } else {
        f(next).foreach { s => queue += s }
      }
    }
    found
  }

хотя я использую только локальную изменчивость (a var и изменчивую Queue), это не чисто функциональный.

Я придумываю другую версию:

  case class State[S](q: Queue[S], cur: S)

  def update[S](f: S => Seq[S])(s: State[S]) : State[S] = {
    val (i, q2) = s.q.dequeue
    val q3 = f(i).foldLeft(q2) { case (acc, i) => acc.enqueue(i)}
    State(q3, i)
  }

  def bfs2[S](init: S, f: S => Seq[S], finalS: S => Boolean): Option[S] = {
    val s = loop(State[S](Queue[S]().enqueue(init), init), update(f) _, (s: State[S]) => s.q.isEmpty || finalS(s.cur))
    Some(s.cur)
  }

  def loop[A](a: A, f: A => A, cond: A => Boolean) : A =
    if (cond(a)) a else loop(f(a), f, cond)

есть ли лучший способ для обоих решений ? Можно ли использовать cats/scalaz для удаления некоторых шаблонов ?

3 ответов


одна хорошая вещь о функциональном программировании вы можете воспользоваться лень отделять обхода структуры данных поиска, часть. Это делает для очень многоразового использования, один код ответственности:

import scala.collection.immutable.Queue

def breadth_first_traverse[Node](node: Node, f: Node => Queue[Node]): Stream[Node] = {
  def recurse(q: Queue[Node]): Stream[Node] = {
    if (q.isEmpty) {
      Stream.Empty
    } else {
      val (node, tail) = q.dequeue
      node #:: recurse(tail ++ f(node))
    }
  }

  node #:: recurse(Queue.empty ++ f(node))
}

Теперь вы можете сделать BFS по breadth_first_traverse(root, f) find (_ == 16) или использовать любую другую функцию в поток класс для выполнения полезных специальных "запросов" на ленивой ширине-сначала сплющенный Stream вашего дерева.


основываясь на ответе, данном Карлом Билефельдтом, вот еще одно решение (которое не включает в себя очередь и просто использует потоки).

def bfs[T](s: Stream[T], f: T => Stream[T]): Stream[T] = {
    if (s.isEmpty) s
    else s.head #:: bfs(s.tail append f(s.head), f)
}

Это непроверено, но я думаю, работает:

  def bfs[S](init: S, f: S => Seq[S], finalS: S => Boolean): Option[S] = {
    def bfshelper(q: Seq[S], f: S => Seq[S], finalS: S => Boolean): Option[S] = q match {
      case Seq()               => None
      case h +: t if finalS(h) => Some(h)
      case h +: t              => bfshelper(t ++ f(h), f, finalS)
    }
    bfshelper(Seq(init), f, finalS)
  }

трюк состоит в том, чтобы сохранить Seq того, что осталось проверить, и, если текущий элемент не совпадает, назовите себя с остатками того, что мы должны были проверить с дочерними элементами этого узла, добавленного