Scala.xml.RuleTransformer的复杂性真的是指数级的吗?



这是我之前一篇文章的后续。

我试图理解为什么RuleTransformer的性能如此差。现在我相信它太慢了,因为它的复杂性是 O(2n),其中 n 是输入 XML 树的高度。

假设我需要将所有元素的所有标签重命名为标签"b":

import scala.xml._, scala.xml.transform._
val rule: RewriteRule = new RewriteRule() {
  override def transform(node: Node): Seq[Node] = node match {
    case e: Elem => e.copy(label = "b")
    case other => other
  }
}
def trans(node: Node): Node = new RuleTransformer(rule).apply(node)

让我们计算一下transform访问输入<a3><a2><a1/></a2></a3>中每个节点的次数。
为了计算访问次数,我们添加一个 缓冲区visited ,在开始时初始化它,存储访问过的节点,并在最后打印它。

import scala.collection.mutable.ListBuffer
// buffer to store visited nodes
var visited: ListBuffer[Node] = ListBuffer[Node]()
val rule: RewriteRule = new RewriteRule() {
  override def transform(n: Node): Seq[Node] = {
    visited append (n) // count this visit
    n match {
      case e: Elem => e.copy(label = "b")
      case other => other
    }
  }
}
def trans(node: Node): Node = {
  visited = ListBuffer[Node]() // init the buffer
  val r = new RuleTransformer(rule).apply(node)
  // print visited nodes and numbers of visits 
  println(visited.groupBy(identity).mapValues(_.size).toSeq.sortBy(_._2))
  r
}

现在让我们在 REPL 中运行它并查看visited

scala> val a3 = <a3><a2><a1/></a2></a3>
a3: scala.xml.Elem = <a3><a2><a1/></a2></a3>
scala> trans(a3)
ArrayBuffer((<a3><b><b/></b></a3>,2), (<a2><b/></a2>,4), (<a1/>,8))
res1: scala.xml.Node = <b><b><b/></b></b>

因此,a1被访问了八次。

如果我们改变<a4><a3><a2><a1/></a2></a3></a4>那么a1将被访问16次,<a5><a4><a3><a2><a1/></a2></a3></a4></a5> - 32次,等等。所以复杂性看起来呈指数级增长。

有意义吗?您将如何通过分析代码来证明这一点?

这不是一个非常严格的分析,但问题似乎出在 BasicTransformer 的 transform(Seq[Node]) 方法[1]。

对于已更改的节点,将调用子转换方法两次。特别是在您的示例中,由于这个原因,所有节点都将被调用两次。你是对的,高度 h 的每个节点将被称为 2^(h-1) 次。另请注意,由于使用 span 和代码,节点的一个子节点最多会被调用两次,在具体示例中,节点的所有子节点都将被调用两次。

只是为了验证这一点,为修改后的RulesTransformer编写了这些代码示例。(我也可以覆盖规则转换器。但无论如何)

// This is same as library RuleTransformer added with print statement
class CopiedRuleTransformer(rules: RewriteRule*) extends BasicTransformer {
    override def transform(n: Node): Seq[Node] = {
        println(n)
        rules.foldLeft(super.transform(n)) { (res, rule) => rule transform res }
    }
}

我修改的规则转换器

class MyRuleTransformer(rules: RewriteRule*) extends BasicTransformer {
    override def transform(n: Node): Seq[Node] = {
        println(n)
        rules.foldLeft(super.transform(n)) { (res, rule) => rule transform res }
    }  
    override def transform(ns:Seq[Node]): Seq[Node] = {
        ns.flatMap(n => transform(n))
    } 
}

这些代码仅用于演示目的。您可以将这些称为

new CopiedRuleTransformer(rule).apply(node)

new MyRuleTransformer(rule).apply(node)

[1] 线 : 34 https://github.com/scala/scala-xml/blob/master/src/main/scala/scala/xml/transform/BasicTransformer.scala

最新更新