xsl-list
[Top] [All Lists]

Re: How efficient is DVC? - A grouping example

2003-03-22 17:22:22
I've done some testing on 1000, 2000, 4000, and 8000 nodes and it seems that
it's growing linear (apart from the sorted step which is probably O(log(N)*N).
However sorting is, much much quicker (sort), so that doesn't show up in the
totals.
The timings include building the binary tree and getting the ranges of nodes.

Cheers,

Robbert


----- Original Message -----
From: "Michael Kay" <mhk(_at_)mhk(_dot_)me(_dot_)uk>
To: <xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com>
Sent: Saturday, March 22, 2003 11:38 PM
Subject: RE: [xsl] How efficient is DVC? - A grouping example


This is fascinating stuff, but the proof of the pudding is in the
eating: have you made any comparative performance measurements, using a
non-trivial input file?

Michael Kay
Software AG
home: Michael(_dot_)H(_dot_)Kay(_at_)ntlworld(_dot_)com
work: Michael(_dot_)Kay(_at_)softwareag(_dot_)com

-----Original Message-----
From: owner-xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
[mailto:owner-xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com] On Behalf Of
Robbert van Dalen
Sent: 22 March 2003 21:07
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Subject: [xsl] How efficient is DVC? - A grouping example


Hello all,

Everyone interested in efficient algorithms might be
interested in this 'article'. Note that I'm not used to write
articles so excuse me if I'm not making a point clearly.

All the code is free - copy it if you like.

Cheers,

RvD

_____________________________________________________________
ABSTRACT

Grouping without using the key() function is not very
difficult in practice. But to implement it efficiently is not
easy task. This article presents a modified Divide And
Conquer (DVC) algorithm to implement grouping. The algorithm
is not only useful for grouping, but may be generalised to
help improve other DVC algorithms.


INTRODUCTION

Muenchian grouping is by far the most efficient method to
group nodes with XSLT. But there are some situations when
Muenchian grouping can't be used, for example if you want to
group tree-fragments Tree-fragments are heavily used when
multiple passes are needed to compute a result with only one
stylesheet. However, you cannot use the key() function on
tree-fragments because XSLT doesn't allow you to (there is no
nodeset parameter)

PREREQUISITES
All examples are tested against XALAN. Make sure you always
include the following stylesheet header:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:xalan="http://xml.apache.org/xalan"; version="1.1"
exclude-result-prefixes="xalan">


GROUPING EXAMPLE
The following example will give you an idea of grouping
tree-fragments without using the key() function.

Input XML (taken from Michael Kay's book)

<cities>
  <city name="Barcelona" country="Espana"/>
  <city name="Paris" country="France"/>
  <city name="Roma" country="Italia"/>
  <city name="Madrid" country="Espana"/>
  <city name="Milano" country="Italia"/>
  <city name="Firenze" country="Italia"/>
  <city name="Napoli" country="Italia"/>
  <city name="Lyon" country="France"/>
</cities>

Stylesheet (partly copied from Michael Kay's book)

<xsl:template match="cities">
  <xsl:variable name="sorted">
    <xsl:for-each select="./city">
      <xsl:sort select="@country"/>
      <xsl:copy-of select="."/>
    </xsl:for-each>
  </xsl:copy-of>

  <xsl:variable name="sorted-tree-fragment"
select="xalan:nodeset($sorted)/*"/>

  <!-- Gets the groups -->
  <xsl:variable name="groups">
    <xsl:apply-templates select="$sorted-tree-fragment"/>
  </xsl:variable>

  <!-- Iterate through all the groups -->
  <xsl:for-each select="xalan:nodeset($groups)/*">
    <xsl:variable name="country" select="."/>
    <xsl:copy>
        <!-- Copy the nodes with the same country -->
      <xsl:copy-of select="$sorted-tree-fragment[country =
$country]"/>
      <xsl:copy-of select="@*"/>
    </xsl:copy>
  </xsl:for-each>
</xsl:template>

<xsl:template match="city">
  <variable name="preceding" select="./preceding-sibling::*[1]"/>
  <xsl:if test="not(./@country = $preceding/@country)">
    <group id="{$preceding/@country}"/>
  </xsl:if>
</xsl:template>

Output:

<group id="Espana">
  <city name="Barcelona" country="Espana"/>
  <city name="Madrid" country="Espana"/>
</group>
<group id="France">
  <city name="Paris" country="France"/>
  <city name="Lyon" country="France"/>
</group>
<group id="Italia">
  <city name="Roma" country="Italia"/>
  <city name="Milano" country="Italia"/>
  <city name="Firenze" country="Italia"/>
  <city name="Napoli" country="Italia"/>
</group>

This looks like a OK solution, but let's get a closer look on
what is going on.

1) First the cities are sorted on the @country attribute.
After this, cities that share the same @country value will be
following each other, which is a property we can exploit in step 2.
2) Then the template that matches city nodes will be called N
times if there are N cities to be grouped. For each city node
in the sorted set the 'following-sibling::*[1]' node(s) are
matched. If they're not equal, the city node will mark a new
group. As Michael Kay already pointed out in his book, the
efficiency of this approach depends on the implementation of
'following-sibling::*[1]'. If this expression has time
complexity O(1) then the overall time complexity of getting
all the groups will be O(N) (leaving sorting out of the equation).
3) Strangely enough, the last step is actually the most
problematic. Let's say the second step gave us 3 groups.
Then, for each group, the expression
'$sorted-tree-fragment[country = $country] will be evaluated
with time complexity O(N).

So, does this mean the overall time complexity will be 3*N =
O(N)? The answer is definitely no! It does hold for a small
number of groups, but if we have N/2 groups then time
complexity will be O(N^2). Selecting nodes with XPATH
expressions is usually OK, but in this example we want to
select the K cities that share the same @country value in
O(K) time, not
O(N) time.
So the question we really want to anwer is: 'how can we
efficiently select a subset of nodes without traversing them
all?'. The anwser is: 'this all depends on the selection
criterium.' Still, if the selection criterium isn't too
complex, we can still hope for a better solution. One
solution is that we don't use XPATH expressions to select
nodes, but rather walk through the nodes by using recursive calls.


GROUPING USING RECURSION

One idea to reduce time complexity of the previous example is
by slightly modifying the match='city' template:

<xsl:template match="city">
  <variable name="preceding" select="./preceding-sibling::*[1]"/>
  <xsl:choose>
    <xsl:when test="not(./@country = $preceding/@country)">
      <group id="{./@country}">
        <xsl:copy-of select="."/>
        <xsl:apply-templates name="./following-sibling::*[1]"/>
      </group>
    </xsl:when>
    <xsl:otherwise>
      <xsl:copy-of select="."/>
      <xsl:apply-templates name="./following-sibling::*[1]"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

If we take the same input and use the following 'apply-templates'

<xsl:apply-templates match="xalan:nodeset($sorted)/*[1]"/>

...we get the following result.

<group id="Espana">
  <city name="Barcelona" country="Espana"/>
  <city name="Madrid" country="Espana"/>
  <group id="France">
    <city name="Paris" country="France"/>
    <city name="Lyon" country="France"/>
    <group id="Italia">
      <city name="Roma" country="Italia"/>
      <city name="Milano" country="Italia"/>
      <city name="Firenze" country="Italia"/>
      <city name="Napoli" country="Italia"/>
    </group>
  </group>
</group>

This is almost what we want. The following 'apply-templates'
will flatten the tree structure to return the same result as
the previous example.

<xsl:apply-templates select="xalan:nodeset($groups)"/>

<xsl:template match="group">
  <xsl:copy>
    <xsl:apply-templates select="./group"/>
    <xsl:copy-of select="./city"/>
    <xsl:copy-of select="@*"/>
  </xsl:copy>
</xsl:template>

The time complexity of the recursive solution can be proven
to be O(N) but with the recursion depth also to be O(N).
Unfortunately, most XSLT implementations have a maximum
recursion depth (~1000) so this is not a general solution.


DVC AND THE BINARY TREE
Dimitre Novatchev was one of the first to mention Divide and
Conquer (DVC) algorithms to reduce recursion depth. Because
most XSLT implementations out there still do not support
tail-recursion elimination, DVC is the way to go if you want
to process a lot of nodes. The idea behind DVC is that to
attack a big problem, you should divide it into a number of
smaller problems. Not surprisingly, dividing a problem into
just 2 subproblems is enough to reduce recursion depth to be
O(log2(N)). The following example will give you an idea of
how this works:


The XML input:

<nodes>
  <node v="1"/>
  <node v="2"/>
  <node v="3"/>
  <node v="4"/>
  <node v="5"/>
  <node v="6"/>
  <node v="7"/>
  <node v="8"/>
</nodes>

The Stylesheet:

<xsl:template match="/">
  <xsl:call-template name="partition">
    <xsl:with-param name="nodes" select="//node"/>
  </xsl:call-template>
</xsl:template>


<xsl:template name="partition">
  <xsl:param name="nodes"/>

  <xsl:variable name="half" select="floor(count($nodes) div 2)"/>

  <b>
  <xsl:choose>
    <xsl:when test="count($nodes) &lt;= 1">
    <!-- There is only one node left: stop dividing problem -->
      <xsl:copy-of select="$nodes"/>
    </xsl:when>
    <xsl:otherwise>
    <!-- divide in first half of nodes (left) -->
        <xsl:call-template name="partition">
          <xsl:with-param name="nodes"
select="$nodes[position() &lt;= $half]"/>
        </xsl:call-template>
    <!-- divide in second half of nodes (right) -->
        <xsl:call-template name="partition">
          <xsl:with-param name="nodes"
select="$nodes[position() &gt; $half]"/>
        </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
  </b>
</xsl:template>

The output:

<b>
  <b>
    <b>
      <b>
        <node v="1"/>
      </b>
      <b>
        <node v="2"/>
      </b>
    </b>
    <b>
      <b>
        <node v="3"/>
      </b>
      <b>
        <node v="4"/>
      </b>
    </b>
  </b>
  <b>
    <b>
      <b>
        <node v="5"/>
      </b>
      <b>
        <node v="6"/>
      </b>
    </b>
    <b>
      <b>
        <node v="7"/>
      </b>
      <b>
        <node v="8"/>
      </b>
    </b>
  </b>
</b>

The result is what is called a binary tree representation. At
first this representation doesn't seem all that useful. Later
we will see that specialised binary trees can be (re-)used to
implement almost any recursive function without exceeding the
maximum recursion depth.

Let's sum all the @v values with the use of the binary
(fragment) tree:

<xsl:template match="/"/>
  <xsl:variable name="btree">
      <xsl:call-template name="partition">
        <xsl:with-param name="nodes" select="//node"/>
      </xsl:call-template>
  </xsl:variable>

  <xsl:call-template name="sum-binary-tree">
    <xsl:with-param name="bnode" select="xalan:nodeset($btree)/*"/>
  </xsl:call-template>
</xsl:template>

<xsl:template name="sum-binary-tree">
  <xsl:param name="bnode"/>

  <xsl:choose>
    <xsl:when test="$bnode/node">
      <xsl:value-of select="$bnode/node/@v"/>
    </xsl:when>
    <xsl:otherwise>
    <xsl:variable name="first">
      <xsl:call-template name="sum-binary-tree">
        <xsl:with-param name="bnode" select="$bnode/b[1]"/>
      </xsl:call-template>
    </xsl:variable>
      <xsl:variable name="second">
      <xsl:call-template name="sum-binary-tree">
        <xsl:with-param name="bnode" select="$bnode/b[2]"/>
      </xsl:call-template>
    </xsl:variable>
      <xsl:value-of select="$first + $second"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

This gives the result: 36

Let's analyse the partition template in terms of time
complexity. It's easy to prove that it is equal to the number
nodes being copied. The partition algorithm uses the XPATH
expression '$nodes[count() &gt; $half]' to split the nodes in
half. This construction is almost exclusively used by all DVC
or 'chunk' algorithms including many of Dimitre Novatchev's
examples. But how about the number of nodes being copied? The
following table lists the number of nodes being copied for
each partition.

partition(1)
  number of copies 1

partition(2)
  number of copies
    l: 1 + partition(1)
    r: 1 + partition(1)

partition(4)
  number of copies
    l: 2 + partition(2)
    r: 2 + partition(2)

partition(8)
  number of copies
    l: 4 + partition(4)
    r: 4 + partition(4)

etc.

The number of copies when calling partition(4) is:
(2 + (1 + 1) + 2 + (1 + 1)) = 2*4

The number of copies when calling partition(8) is:
4 + (2 + (1 + 1) + 2 + (1 + 1)) + 4 + (2 + (1 + 1) + 2 + (1 +
1)) = 3*8

So the overall 'copy' complexity is O(log2(N)*N).
Although the number of recursive calls is O(N) the XSLT
processor still spends at least O(log2(N)*N) time because it
must copy (and select) half of the nodes for the each
recursive call (twice). Copying nodes should be avoided as
much as possible because it slows down recursion considerably.


MODIFIED DVC ALGORITHM: RANGE PARTITIONING

The following implementation of a binary partition doesn't
copy a list of nodes but just one node at each recursive
call. It uses the so called 'sibling' axis to walk through
the list. Because there are O(N) recursive calls, this means
that O(N) nodes are copied. Does this mean that the overall
time complexity will be O(N) too? The answer is: probably
yes, but at worst it will be O(N^2).

Input XML:

<nodes>
  <node v="1"/>
  <node v="2"/>
  <node v="3"/>
  <node v="4"/>
  <node v="5"/>
  <node v="6"/>
  <node v="7"/>
  <node v="8"/>
</nodes>

The Stylesheet:

<xsl:template match="/">
  <xsl:call-template name="partition-ranges">
    <xsl:with-param name="node" select="//node[1]"/>
  </xsl:call-template>
</xsl:template>

<xsl:template name="partition-ranges">
  <xsl:param name="node"/>
  <xsl:param name="s"
select="(count($node/preceding-sibling::*)) + 1"/>
  <xsl:param name="e"
select="(count($node/following-sibling::*)) + $s"/>

  <xsl:if test="$node">
    <xsl:element name="r">
      <xsl:attribute name="s">
        <xsl:value-of select="$s"/>
      </xsl:attribute>
      <xsl:attribute name="e">
        <xsl:value-of select="$e"/>
      </xsl:attribute>
      <xsl:choose>
        <xsl:when test="$s = $e">
            <xsl:copy-of select="$node"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name="w" select="floor(($e - $s + 1) div 2)"/>
          <xsl:variable name="m" select="$s + $w"/>
          <xsl:call-template name="partition-ranges">
            <xsl:with-param name="node" select="$node"/>
            <xsl:with-param name="s" select="$s"/>
            <xsl:with-param name="e" select="$m - 1"/>
          </xsl:call-template>
          <xsl:call-template name="partition-ranges">
            <xsl:with-param name="node"
select="$node/following-sibling::*[$w]"/>
            <xsl:with-param name="s" select="$m"/>
            <xsl:with-param name="e" select="$e"/>
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:element>
  </xsl:if>
</xsl:template>

The output:

<r s="1" e="8">
  <r s="1" e="4">
    <r s="1" e="2">
      <r s="1" e="1">
        <node v="1"/>
      </r>
      <r s="2" e="2">
        <node v="2"/>
      </r>
    </r>
    <r s="3" e="4">
      <r s="3" e="3">
        <node v="3"/>
      </r>
      <r s="4" e="4">
        <node v="4"/>
      </r>
    </r>
  </r>
  <r s="5" e="8">
    <r s="5" e="6">
      <r s="5" e="5">
        <node v="5"/>
      </r>
      <r s="6" e="6">
        <node v="6"/>
      </r>
    </r>
    <r s="7" e="8">
      <r s="7" e="7">
        <node v="7"/>
      </r>
      <r s="8" e="8">
        <node v="8"/>
      </r>
    </r>
  </r>
</r>

Note that the output resembles the previous example but
instead of <b> nodes, <r> (Range) nodes are used. This just
makes it more convenient to select ranges of nodes later on.
The actual 'splitting' is done through the following
expression '[$node/following-sibling::*[$w]' with $w being
the lenght of the list divided by 2. Let's compare overall
time complexity with the possible implementations of
'following-sibling::[w]'

following-sibling::*[w] | total time
_____________________________________
O(1)                    | O(N)
O(w)                    | O(log2(N)*N)
O(N)                    | O(N^2)

So at worst it will be quadratic. So the question still
remains if it is theoretically possible to do binary
partitioning without copying to much nodes. Nevertheless,
experiments with XALAN have shown that the implementation is
not quadratic.


GROUPING WITH A BINARY TREE

The new and improved grouping algorithm is more or less the
same as the first one except where using ranges to select
nodes which are in the same group.
Thus:

1) we sort the nodes for a given key
2) then compute the ranges of nodes which have the same key
3) and then select the (sorted) nodes for each range.

To efficiently select a range of nodes we will be using the
binary tree.

Here's the whole solution:

Input XML:

<cities>
  <city name="Barcelona" country="Espana"/>
  <city name="Paris" country="France"/>
  <city name="Roma" country="Italia"/>
  <city name="Madrid" country="Espana"/>
  <city name="Milano" country="Italia"/>
  <city name="Firenze" country="Italia"/>
  <city name="Napoli" country="Italia"/>
  <city name="Lyon" country="France"/>
</cities>


The stylesheet (WARNING, THIS IS A BIT LENGHTY):

<!-- Group cities on country -->
<xsl:template match="/">
  <xsl:call-template name="group-on-key">
    <xsl:with-param name="nodes" select="//city"/>
    <xsl:with-param name="key" select="'country'"/>
  </xsl:call-template>
</xsl:template>

<!--
  Template: group-on-key
  Use this template to group <nodes> which share a common
attribute <key>
  The result will be sub-sets of <nodes> surrounded by <group/> tags
-->


<xsl:template name="group-on-key">
  <xsl:param name="nodes"/>
  <xsl:param name="key"/>

  <xsl:variable name="items">
    <xsl:for-each select="$nodes">
      <item>
        <key>
          <xsl:value-of select="./@*[name() = $key]"/>
        </key>
        <value>
          <xsl:copy-of select="."/>
        </value>
      </item>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="grouped-items">
    <xsl:call-template name="group-on-item">
      <xsl:with-param name="nodes" select="xalan:nodeset($items)/*"/>
      <xsl:with-param name="key" select="$key"/>
    </xsl:call-template>
  </xsl:variable>

  <xsl:for-each select="xalan:nodeset($grouped-items)/*">
    <xsl:copy>
      <xsl:for-each select="./*">
        <xsl:copy-of select="./value/*[1]"/>
      </xsl:for-each>
    </xsl:copy>
  </xsl:for-each>
</xsl:template>

<!--
 Template: group-on-item
 Use this template to group <nodes> which share a common
structure.  You can build this structure yourself if you want
to group on something else

 The structure is the <item> structure and has the following
layout  <item>
  <key>
   aKey (can be anything, preferrably a string)
  </key>
  <value>
   aValue (can be anything, probably a node(set))
  </value>
 </item>

 <items> will we grouped on the string value of <key>
 The result will be sub-sets of <items> surrounded by <group/> tags
-->

<xsl:template name="group-on-item">
  <xsl:param name="nodes"/>

  <!-- Step 1 -->
  <xsl:variable name="sorted">
    <xsl:for-each select="$nodes">
      <xsl:sort select="./key[1]/"/>
      <xsl:copy-of select="."/>
    </xsl:for-each>
  </xsl:variable>

  <xsl:variable name="sorted-tree" select="xalan:nodeset($sorted)/*"/>

  <!-- Step 2.1 -->
  <xsl:variable name="pivots">
    <xsl:call-template name="pivots">
      <xsl:with-param name="nodes" select="$sorted-tree"/>
    </xsl:call-template>
  </xsl:variable>

  <!-- Step 2.2 -->
  <xsl:variable name="ranges">
    <xsl:call-template name="ranges">
      <xsl:with-param name="pivots"
select="xalan:nodeset($pivots)/*"/>
      <xsl:with-param name="length" select="count($sorted-tree)"/>
    </xsl:call-template>
  </xsl:variable>

  <!-- Step 3.1 -->
  <xsl:variable name="partition-ranges">
    <xsl:call-template name="partition-ranges">
      <xsl:with-param name="node" select="$sorted-tree[1]"/>
    </xsl:call-template>
  </xsl:variable>

  <xsl:variable name="root-partition"
select="xalan:nodeset($partition-ranges)/*[1]"/>

  <!-- Step 3.2 -->
  <xsl:for-each select="xalan:nodeset($ranges)/r">
    <xsl:variable name="s" select="./@s"/>
    <xsl:variable name="e" select="./@e"/>

    <group>
      <xsl:call-template name="range-in-partition">
        <xsl:with-param name="s" select="$s"/>
        <xsl:with-param name="e" select="$e"/>
        <xsl:with-param name="p" select="$root-partition"/>
      </xsl:call-template>
    </group>
  </xsl:for-each>

</xsl:template>

<xsl:template name="pivots">
  <xsl:param name="nodes"/>
  <xsl:param name="key"/>

  <xsl:for-each select="$nodes">
    <xsl:if test="not(string(./key[1]/) =
string(./following-sibling::*[1]/key[1]/))">
      <pivot>
        <xsl:value-of select="position()"/>
      </pivot>
    </xsl:if>
  </xsl:for-each>
</xsl:template>

<xsl:template name="ranges">
  <xsl:param name="pivots" select="../"/>
  <xsl:param name="length" select="0"/>

  <xsl:choose>
  <xsl:when test="count($pivots) &gt;= 1">
  <xsl:for-each select="$pivots">
    <xsl:variable name="p" select="./preceding-sibling::*[1]"/>
    <r>
      <xsl:attribute name="s">
        <xsl:choose>
          <xsl:when test="$p">
            <xsl:value-of select="$p + 1"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="1"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:attribute>
      <xsl:attribute name="e">
        <xsl:value-of select="string(.)"/>
      </xsl:attribute>
    </r>
  </xsl:for-each>
  </xsl:when>
  <xsl:otherwise>
    <r>
      <xsl:attribute name="s">
        <xsl:value-of select="1"/>
      </xsl:attribute>
      <xsl:attribute name="e">
        <xsl:value-of select="$length"/>
      </xsl:attribute>
    </r>
  </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<!--
 Template: range-in-partition
 Selects a RANGE of nodes using a binary tree

 XSLT isn't really helping to make things easy but try to do
this in a DVC style directly without the help of a binary tree.
-->

<xsl:template name="range-in-partition">
  <xsl:param name="p"/>
  <xsl:param name="s" select="$p/@s"/>
  <xsl:param name="e" select="$p/@e"/>

  <xsl:variable name="ps" select="number($p/@s)"/>
  <xsl:variable name="pe" select="number($p/@e)"/>

  <xsl:if test="$s &lt;= $pe and $e &gt;= $ps">
    <xsl:if test="$ps = $pe">
      <xsl:copy-of select="$p/*[1]"/>
    </xsl:if>
    <xsl:choose>
      <xsl:when test="$s &gt; $ps">
        <xsl:variable name="s1" select="$s"/>
        <xsl:choose>
          <xsl:when test="$e &lt; $pe">
            <xsl:variable name="e1" select="$e"/>
            <xsl:for-each select="$p/*">
              <xsl:call-template name="range-in-partition">
                <xsl:with-param name="s" select="$s1"/>
                <xsl:with-param name="e" select="$e1"/>
                <xsl:with-param name="p" select="."/>
              </xsl:call-template>
            </xsl:for-each>
          </xsl:when>
          <xsl:otherwise>
            <xsl:variable name="e1" select="$pe"/>
            <xsl:for-each select="$p/*">
              <xsl:call-template name="range-in-partition">
                <xsl:with-param name="s" select="$s1"/>
                <xsl:with-param name="e" select="$e1"/>
                <xsl:with-param name="p" select="."/>
              </xsl:call-template>
            </xsl:for-each>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <xsl:variable name="s1" select="$ps"/>
        <xsl:choose>
          <xsl:when test="$e &lt; $pe">
            <xsl:variable name="e1" select="$e"/>
            <xsl:for-each select="$p/*">
              <xsl:call-template name="range-in-partition">
                <xsl:with-param name="s" select="$s1"/>
                <xsl:with-param name="e" select="$e1"/>
                <xsl:with-param name="p" select="."/>
              </xsl:call-template>
            </xsl:for-each>
          </xsl:when>
          <xsl:otherwise>
            <xsl:variable name="e1" select="$pe"/>
            <xsl:for-each select="$p/*">
              <xsl:call-template name="range-in-partition">
                <xsl:with-param name="s" select="$s1"/>
                <xsl:with-param name="e" select="$e1"/>
                <xsl:with-param name="p" select="."/>
              </xsl:call-template>
            </xsl:for-each>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
</xsl:template>

Output XML:

<group>
  <city name="Barcelona" country="Espana"/>
  <city name="Madrid" country="Espana"/>
</group>
<group>
  <city name="Paris" country="France"/>
  <city name="Lyon" country="France"/>
</group>
<group>
  <city name="Roma" country="Italia"/>
  <city name="Milano" country="Italia"/>
  <city name="Firenze" country="Italia"/>
  <city name="Napoli" country="Italia"/>
</group>


CONCLUSION

An efficient DVC algorithm is given for grouping using a
binary tree. That binary trees can be build with time
complexity O(N) and 'copy' complexity O(N) - without relying
to much on implementations - is still an open question.



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list




 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list