我将传入的对象流转换为树状数据结构,使用不同的类来表示树中不同类型的节点。为了处理流,我有许多相互递归的函数,它们将根据流中的下一个对象进行分支,相互调用以执行部分处理,最后在树中生成单个节点;换句话说,这或多或少是一种递归下降解析器。
目前我正在使用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)
}
然而,这是非常麻烦的,我需要创建所有额外的虚拟变量,并跟踪将流的其余部分传递给下一个函数,以及所需的常量元组解包。是否有更简单的方法来处理流,或者更方便地解决问题?
答案 0 :(得分:0)
有很多强大的库,如play-iteratees或scalaz-stream,但我会为您提供简单的解决方案,以便了解这些库。
一般的想法是:
Elem
定义状态而不是相互递归来遍历这些流,每个状态都产生下一个状态,可能还有一些Result
所以声明应该像
type State[Elem, Result] = Elem => (State[Elem,Result], Option[Result])
Stream[Elem] => Stream[Result]
来处理项目。所以让我们声明我们的通用状态容器:
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))
的很多数字的总和
让我们回到你的案例。树木建筑等案例怎么样?假设我们按照以下表示法在整数流中编码树结构:
StackOverflowError
节点子项count
他们因此,如果我们针对像
这样的结构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
期待孩子数量,如果NodeChildCnt
和Leaf
可能会产生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)