在XSLT中对记录进行分组时如何避免O(n ^ 2)的复杂性?

时间:2011-11-10 09:05:44

标签: java performance xslt xerces xalan

当XSL将大量数据转换为HTML时,我经常遇到性能问题。这些数据通常只是几个非常大的表格,大致有这种形式:

<table>
  <record>
    <group>1</group>
    <data>abc</abc>
  </record>
  <record>
    <group>1</group>
    <data>def</abc>
  </record>
  <record>
    <group>2</group>
    <data>ghi</abc>
  </record>
</table>

在转换过程中,我希望在视觉上对这些记录进行分组

+--------------+
| Group 1      |
+--------------+
|   abc        |
|   def        |
+--------------+
| Group 2      |
+--------------+
|   ghi        |
+--------------+

这是一个愚蠢的实现(设置来自http://exslt.org。实际实现有点不同,这只是一个例子):

<xsl:for-each select="set:distinct(/table/record/group)">
  <xsl:variable name="group" select="."/>

  <!-- This access needs to be made faster : -->
  <xsl:for-each select="/table/record[group = $group]">
    <!-- Do the table stuff -->
  </xsl:for-each>
</xsl:for-each>

很容易看出这往往会有O(n^2)的复杂性。更糟糕的是,因为每个记录中都有很多字段。操作的数据可以达到几十MB,记录的数量可以达到5000.在最坏的情况下,每个记录都有自己的组和50个字段。而且为了使事情变得更糟,还有另一种分组可能,这就是O(n^3)

现在会有很多选择:

  1. 我可以找到一个涉及地图和嵌套数据结构的Java解决方案。但我想提高我的XSLT技能,所以这实际上是最后一个选择。
  2. 我可能忘记了Xerces / Xalan / Exslt中的一个很好的功能,可以更好地处理分组
  3. 我可以为/table/record/group
  4. 构建某种索引
  5. 您可以向我证明,<xsl:apply-templates/>方法在此用例中的速度明显快于<xsl:for-each/>方法。
  6. 您如何看待这种O(n^2)复杂性如何降低?

4 个答案:

答案 0 :(得分:4)

如果数据按组预先排序(如示例所示),则可以循环记录集并检查记录组是否与前一个记录组不同。如果组更改,您可以添加组标题。这将以O(n)时间复杂度执行。

答案 1 :(得分:4)

您可以在XSLT 1.0中使用众所周知的Muenchian分组方法 - 无需探索排序数据并实现更复杂和更慢的算法:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kGroupByVal" match="group" use="."/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match=
  "group
      [generate-id()
      =
       generate-id(key('kGroupByVal', .)[1])
      ]">
  <group gid="{.}">
   <xsl:apply-templates select="key('kGroupByVal', .)/node()"/>
  </group>
 </xsl:template>
 <xsl:template match="group/text()"/>
</xsl:stylesheet>

将此转换应用于您提供的文本(这甚至不是格式良好的XML文档!!!)在将其修改为格式良好后,

3个record元素需要80毫秒

类似的文字包含1000个record元素,转换完成时间为136毫秒

使用10000 record个元素所需时间为284毫秒

使用100000个record元素所需时间为1667毫秒

观察到的复杂性显然是次线性的。

在XSLT 1.0中找到比Muenchian分组更有效的解决方案是非常困难的(如果可能的话)。

答案 2 :(得分:2)

您当前的算法:

for every [group] record
  for every [data] record
    // actions

我假设你执行所有元素和

的简单迭代
 for every [record]
       take [data]
       take [group]
       add [data] to [group]

对于群组表示,您可以使用树木或地图。

如您所见,此算法具有复杂度O(n)

答案 3 :(得分:2)

推荐的分组方法是XSLT 2.0中的xsl:for-each-group和XSLT 1.0中的Muenchian分组。使用任何半合适的处理器,这两者都具有(n * log(n))性能。

或者您可以通过调用key()函数简单地替换"/table/record[group = $group]"

如果您准备为Saxon-EE等企业级XSLT处理器付费,那么很可能会自动为您完成这些优化,因此您不必担心它们。