在使用相互递归函数时,是否有一种简单的方法来处理Streams?

时间:2015-05-15 16:34:59

标签: scala

我将传入的对象流转换为树状数据结构,使用不同的类来表示树中不同类型的节点。为了处理流,我有许多相互递归的函数,它们将根据流中的下一个对象进行分支,相互调用以执行部分​​处理,最后在树中生成单个节点;换句话说,这或多或少是一种递归下降解析器。

目前我正在使用Iterator来表示对象流。迭代器的有状态使得它相对容易使用,因为每个函数只需要根据需要向前移动迭代器,并且所有其他函数将自动继续前一个函数停止的位置,而不必明确地跟踪当前位置在溪流中。

然而,需要实现各种先行功能的组合,以及希望使解析器更具功能并更好地利用Scala的模式匹配功能的组合,最近使得这种状态有点难以处理,例如,我需要跟踪已经消耗的元素以实现前瞻。

作为潜在的替代品,我已经了解了Scala中功能更强的Stream类;但是,使用Stream需要我明确地跟踪当前位置,以便其他函数将从流的正确部分读取。我已尝试让每个函数从一个参数中读取一个流,并返回尚未作为元组的一部分处理的流的部分以及实际的返回值,例如:

def f2(s: Stream[X])() = {
  val a #:: s2 = s
  val (b, s3) = a match {
    case A => f2(s2)() match { case (x, s) => (Some(x), s) }
    case _ => (None, s2)
  }
  val (c, s4) = f3(s3)(1, 2, 3)
  val (d, s5) = f4(s4)()
  val e #:: s6 = s5
  (new MyClass(a, b, c, d, e), s6)
}

然而,这是非常麻烦的,我需要创建所有额外的虚拟变量,并跟踪将流的其余部分传递给下一个函数,以及所需的常量元组解包。是否有更简单的方法来处理流,或者更方便地解决问题?

1 个答案:

答案 0 :(得分:0)

有很多强大的库,如play-iterateesscalaz-stream,但我会为您提供简单的解决方案,以便了解这些库。

一般的想法是:

  1. 我们有一些Elem
  2. 类型的元素流
  3. 定义状态而不是相互递归来遍历这些流,每个状态都产生下一个状态,可能还有一些Result所以声明应该像

    type State[Elem, Result] = Elem => (State[Elem,Result], Option[Result])
    
  4. 定义一些函数Stream[Elem] => Stream[Result]来处理项目。
  5. 所以让我们声明我们的通用状态容器:

     class PStateM[Elem, Result](val receiveM: Option[Elem] => (Option[PStateM[Elem, Result]], Option[Result]))
    

    首先 P 用于进程,最后 M 用于 Monadic ,其中monad是{{1} (但是后来可能会更像强大的东西,如play-teratee中的Option

    我们还有两个Future比较第2点。

    Option表示我们可以告诉我们的州,流已超过Option[Elem]作为来源。

    结果的第一部分中的None意味着我们可以更早地结束计算。

    让我们定义更轻的版本,它与第2点精确相似:

    Option[PStateM[Elem, Result]]

    Nest让我们定义我们的过程函数,如第3点所述:

      class PState[Elem, Result](val receive: Elem => (PStateM[Elem, Result], Option[Result])) extends PStateM[Elem, Result]({
        case Some(elem) => receive(elem) match {case (next, res) => (Some(next), res)}
        case None => (None, None)
      })
    

    男孩,有很多 def process[Elem, Result](state: PStateM[Elem, Result], source: Stream[Elem]): Stream[Result] = source match { case Stream() => state.receiveM(None)._2.toStream case head #:: tail => { val (next, res) = state.receiveM(Some(head)) next match { case None => res.toStream case Some(nState) => res match { case None => process(nState, tail) case Some(res) => res #:: process(nState, tail) } } } } es,但是这种深度允许我们的函数根据情况被尾递归或延迟。 让我们来证明一下。我们将定义三个用于处理流的关键处理器:

    match

    如果我们使用

    之类的话
      case class MapS[A, B](f: A => B) extends PState[A, B](elem => (MapS(f), Some(f(elem))))
    
      case class FoldS[A, B](z: B, f: (B, A) => B) extends PStateM[A, B]({
        case Some(elem) => (Some(FoldS(f(z, elem), f)), None)
        case None => (None, Some(z))
      })
    
      case class FilterS[A](f: A => Boolean) extends PState[A, A](elem => (FilterS(f), Some(elem) filter f))
    

    我们会看到前10个平均值,前10个赔率以及没有println(process(MapS[Int, Int](_ * 2), Stream.from(1)).take(10).toList) println(process(FilterS[Int](_ % 2 == 1), Stream.from(1)).take(10).toList) println(process(FoldS[Int, Long](0L, _ + _.toLong), 1 to 100000 toStream)) 的很多数字的总和

    让我们回到你的案例。树木建筑等案例怎么样?假设我们按照以下表示法在整数流中编码树结构:

    1. 首先我们有整数节点密钥
    2. 我们得到StackOverflowError节点子项
    3. 接下来,我们获取所有孩子的节点键 - 正好count他们
    4. 生完孩子后我们回到第1步
    5. 因此,如果我们针对像

      这样的结构
      count

      我们可以定义我们的处理器:

        trait Node
        case class Branch(elem: Int, children: IndexedSeq[Int]) extends Node
        case class Leaf(elem: Int) extends Node
      

      有三个阶段:case class NodeElem() extends PState[Int, Node](elem => (NodeChildCnt(elem), None)) case class NodeChildCnt(elem: Int) extends PState[Int, Node]({ case 0 => (NodeElem(), Some(Leaf(elem))) case count => (NodeChildElem(elem, count, IndexedSeq.empty), None) }) case class NodeChildElem(elem: Int, count: Int, children: IndexedSeq[Int]) extends PStateM[Int, Node]({ case Some(child) => if (count > 1) (Some(NodeChildElem(elem, count - 1, children :+ child)), None) else (Some(NodeElem()), Some(Branch(elem, children :+ child))) case None => (None, Some(Branch(elem, children))) }) 期待节点密钥,NodeElem期待孩子数量,如果NodeChildCntLeaf可能会产生0按孩子收集孩子,减少剩余人数。

      这个命令

      NodeChildElem

      使用不完整的第5个节点打印预期结构:

      println(process(NodeElem, "1 2 2 3 2 2 4 5 3 0 4 0 5 4 6 7".split(' ').toStream.map(_.toInt)).toList)