假设您有一个高度合成的任务,在没有适当的输入XML的情况下打印1到1.000.000之间的数字。 当然,由于讽刺的是,堆栈溢出会导致直接递归失败。
我想出了下面列出的解决方案:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="end" select="number(1000000)"/>
<xsl:template match="/">
<xsl:call-template name="batches"/>
</xsl:template>
<xsl:template name="batches">
<xsl:param name="start" select="number(1)"/>
<xsl:param name="stop" select="$end"/>
<xsl:param name="ololo"/>
<xsl:if test="$start <= ($end)">
<xsl:choose>
<xsl:when test="$stop = 0">
<xsl:value-of select="$start"/>:<xsl:value-of select="$ololo"/>
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="batches">
<xsl:with-param name="start" select="$start"/>
<xsl:with-param name="stop" select="floor($stop div 2)"/>
<xsl:with-param name="ololo" select=" 'A' "/>
</xsl:call-template>
<xsl:call-template name="batches">
<xsl:with-param name="start" select="floor($stop div 2) + $start + 1"/>
<xsl:with-param name="stop" select="floor($stop div 2)"/>
<xsl:with-param name="ololo" select=" 'B' "/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
它在libxslt和MSXML中都有效。但它打印出一些重复的数字,在效率方面看起来很尴尬。这可以以某种方式得到改善吗?
答案 0 :(得分:12)
这是一个通用的“迭代”模板,它对初始输入执行操作,然后对其结果执行操作,直到指定给定条件。
这个转换是 tail-recursive ,并且在没有堆栈溢出的情况下使用智能XSLT处理器:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:action>
<end>1000000</end>
</my:action>
<xsl:variable name="vAction"
select="document('')/*/my:action"/>
<xsl:template match="/">
<xsl:call-template name="iterate">
<xsl:with-param name="pAction" select="$vAction"/>
<xsl:with-param name="pInput" select="0"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="iterate">
<xsl:param name="pAction"/>
<xsl:param name="pInput"/>
<xsl:if test="string-length($pInput)">
<xsl:variable name="vResult">
<xsl:apply-templates select="$pAction">
<xsl:with-param name="pInput" select="$pInput"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:copy-of select="$vResult"/>
<xsl:call-template name="iterate">
<xsl:with-param name="pAction"
select="$pAction"/>
<xsl:with-param name="pInput" select="$vResult"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="my:action">
<xsl:param name="pInput" select="0"/>
<xsl:if test="not($pInput >= end)">
<xsl:value-of select="concat($pInput+1,'
')"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
当此转换应用于任何XML文档(未使用)时,优化尾递归到迭代的智能XSLT处理器会产生所需的结果而不会发生堆栈溢出。 Saxon 6.5.4就是这种情况,我用它来产生结果。
您的问题是并非所有XSLT处理器都能识别并优化尾递归。
对于此类处理器,可以使用DVC样式的递归:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="1000000"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="displayNumbers">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:if test="not($pStart > $pEnd)">
<xsl:choose>
<xsl:when test="$pStart = $pEnd">
<xsl:value-of select="$pStart"/>
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vMid" select=
"floor(($pStart + $pEnd) div 2)"/>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$pStart"/>
<xsl:with-param name="pEnd" select="$vMid"/>
</xsl:call-template>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$vMid+1"/>
<xsl:with-param name="pEnd" select="$pEnd"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
此转换使用MSXML4生成正确的结果而不会发生任何崩溃。
通过此DVC转换,最大递归深度仅为Log2(N) - 在本例中为19。
我建议使用FXSL library 。它提供了常用高阶函数的DVC变体,例如foldl()
和map()
,可以生成几乎任何递归算法的DVC变体。
当然,在XSLT2.0中,只需编写:
<xsl:sequence select="1 to 1000000"/>