<xsl:templatename="ckbk:sum"><!-- инициализировать пустой набор узлов --><xsl:paramname="nodes"select="/.."/><xsl:paramname="result"select="'0'"/><xsl:choose><xsl:whentest="not($nodes)"><xsl:value-ofselect="$result"/></xsl:when><xsl:otherwise><!-- вызвать или применить шаблон, который определит значениеузла в случае, когда сам узел не является суммируемым значением --><xsl:variablename="value"><xsl:call-templatename="some-function-of-a-node"><xsl:with-paramname="node"select="$nodes[1]"/></xsl:call-template></xsl:variable><!-- рекурсивно просуммировать по оставшейся части набора --><xsl:call-templatename="ckbk:sum"><xsl:with-paramname="nodes"select="$nodes[position() != 1]"/><xsl:with-paramname="result"select="$result + $value"/></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template>
Для обработки большого числа узлов при отсутствии поддержки хвостовой рекурсии есть два способа. Первый обычно называют разделяй и властвуй. Его идея в том, чтобы на каждом шаге рекурсии уменьшить объем работы по меньшей мере вдвое.
<xsl:templatename="ckbk:sum-dvc"><xsl:paramname="nodes"select="/.."/><xsl:paramname="result"select="'0'"/><xsl:paramname="dvc-threshold"select="100"/><xsl:choose><xsl:whentest="count($nodes) <= $dvc-threshold"><xsl:call-templatename="ckbk:sum"><xsl:with-paramname="nodes"select="$nodes"/><xsl:with-paramname="result"select="$result"/></xsl:call-template></xsl:when><xsl:otherwise><xsl:variablename="half"select="floor(count($nodes) div 2)"/><xsl:variablename="sum1"><xsl:call-templatename="ckbk:sum-dvc"><xsl:with-paramname="nodes"select="$nodes[position() <= $half]"/><xsl:with-paramname="result"select="$result"/><xsl:with-paramname="dvc-threshold"select="$dvc-threshold"/></xsl:call-template></xsl:variable><xsl:call-templatename="ckbk:sum-dvc"><xsl:with-paramname="nodes"select="$nodes[position() > $half]"/><xsl:with-paramname="result"select="$sum1"/><xsl:with-paramname="dvc-threshold"select="$dvc-threshold"/></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template>
Второй способ называется разбиением на части. На первом этапе большая задача разбивается на части разумного размера, а на втором каждая часть обрабатывается рекурсивно.
<xsl:templatename="ckbk:product"><xsl:paramname="nodes"select="/.."/><xsl:paramname="result"select="1"/><xsl:choose><xsl:whentest="not($nodes)"><xsl:value-ofselect="$result"/></xsl:when><xsl:otherwise><!-- вызвать или применить шаблон, который определит значение узла в случае, когда сам узел не входит в число перемножаемых значений --><xsl:variablename="value"><xsl:call-templatename="some-function-of-a-node"><xsl:with-paramname="node"select="$nodes[1]"/></xsl:call-template></xsl:variable><xsl:call-templatename="ckbk:product"><xsl:with-paramname="nodes"select="$nodes[position() != 1]"/><xsl:with-paramname="result"select="$result * $value"/></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template>
<xsl:templatename="ckbk:product-dvc"><xsl:paramname="nodes"select="/.."/><xsl:paramname="result"select="1"/><xsl:paramname="dvc-threshold"select="100"/><xsl:choose><xsl:whentest="count($nodes) <= $dvc-threshold"><xsl:call-templatename="ckbk:product"><xsl:with-paramname="nodes"select="$nodes"/><xsl:with-paramname="result"select="$result"/></xsl:call-template></xsl:when><xsl:otherwise><xsl:variablename="half"select="floor(count($nodes) div 2)"/><xsl:variablename="product1"><xsl:call-templatename="ckbk:product-dvc"><xsl:with-paramname="nodes"select="$nodes[position() <= $half]"/><xsl:with-paramname="result"select="$result"/><xsl:with-paramname="dvc-threshold"select="$dvc-threshold"/></xsl:call-template></xsl:variable><xsl:call-templatename="ckbk:product-dvc"><xsl:with-paramname="nodes"select="$nodes[position() > $half]"/><xsl:with-paramname="result"select="$product1"/><xsl:with-paramname="dvc-threshold"select="$dvc-threshold"/></xsl:call-template></xsl:otherwise></xsl:choose></xsl:template>
Лучший способ вычисления простых сумм – это встроенная в XPath функция sum(). Но, если надо вычислить сумму значений произвольной функции от узлов, то приходится делать одно из двух:
применять рецепты из этого раздела либо
сначала вычислить значения функции от всех узлов и сохранить результат в переменной в виде фрагмента дерева. Затем вызвать функцию расширение для преобразования фрагмента в набор узлов, который можно подать на вход функции sum. В XSLT 2.0 обобщенное суммирование становится тривиальной задачей из-за исчезновения результирующих фрагментов дерева.
Однако в XPath нет встроенной функции prod(), так что для перемножения чисел придется написать собственную:
12345678
<xsl:functionname="ckbk:prod"as="xs:double"><xsl:paramname="numbers"as="xs:double*"/><xsl:sequenceselect="if (count($numbers) eq 0) then 0else if (count($numbers) = 1) then $numbers[1]else $numbers[1] * ckbk:prod(subsequence($numbers,2))"/></xsl:function>
Методы разбиения на части и «разделяй и властвуй» оказываются полезны, когда нужно рекурсивно обработать потенциально большой набор узлов. Эксперименты показывают, что даже при наличии процессора XSLT, оптимизирующего хвостовую рекурсию, применение этих методов позволяет повысить производительность.
В главе 14 показано, как написать повторно используемую оснастку для запуска алгоритмов разбиения на части и «разделяй и властвуй».
При наличии функции prod возникает искушение вычислить факториал следующим образом:
123456
<xsl:functionname="ckbk:factorial"as="xs:double"><xsl:paramname="n"as="xs:integer"/><xsl:sequenceselect="if ($n eq 0) then 1 else ckbk:prod(1 to $n)"/></xsl:function>
Если $n мало, то это решение, может, и сгодится, но при увеличении $n накладные расходы на построение последовательности могут ухудшить производительность по сравнению с непосредственным решением. Об этом всегда следует помнить, прибегая к последовательностям. Майкл Кэй приводит и другие примеры в ходе обсуждения различий между решениями задачи о рыцарском турнире для версий 1.0 и 2.0 (XSLT 2.0, Third Edition, Wrox, 2004, стр. 753).
Димитр Новачев (Dimitre Novatchev) и Славомир Тышко (Slawomir Tyszko) сравнивают методы разбиения на части и «разделяй и властвуй» в статье на странице.