计算每个节点的Strahler编号

时间:2015-10-25 21:57:55

标签: xslt xquery

节点的Strahler number 在树中是节点高度概念的概括。

目标是定义一个转换,它将数字属性(例如strahler)添加到XML文档的每个节点。

归纳地表示n的节点Stn(n)的Strahler编号定义如下:

  • 如果n是一片叶子,那么Stn(n)=0
  • 其他(n不是叶子)通过降序Strahler值对其子项(n1,...,nd)进行排序:Stn(n1)≥{{1} }≥ ......≥ Stn(n2)。 如果Stn(nd)Stn(n1) = Stn(n2);否则Stn(n)=n1+1

E.g。对于带有子Stn(n)=Stn(n1) n n1的节点n2,分别为Stn n3 1 2,{{1}的Stn }是3(因为最大值只出现一次)。对于带有子n 3 n的节点n1,分别为Stn n2 n3 1,{{1}的Stn再次2(因为最大值出现不止一次)。

示例输入

2

输出

n

3 个答案:

答案 0 :(得分:1)

这种简短而有效的XSLT 2.0转换

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:saxon="http://saxon.sf.net/"
 xmlns:f="my:f">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

  <xsl:template match="*[true()]">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:attribute name="strahler" select="f:strahler(.)"/>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:function name="f:strahler" as="xs:integer" saxon:memo-function="yes">
    <xsl:param name="pElem" as="element()"/>

    <xsl:sequence select=
     "if(not($pElem/*))
        then 1
        else 
           if(not($pElem/*[2]))
             then f:strahler($pElem/*[1])
             else for $maxVal in max($pElem/*/f:strahler(.)),
                      $twoWithMax in 
                           (($pElem/*[f:strahler(.) eq $maxVal])[2]/1, 0)[1]
                    return $maxVal+$twoWithMax
     "/>
  </xsl:function>
</xsl:stylesheet>

应用于提供的XML文档

<root field="4">
    <a>
        <aa x="1"/>
        <ab>
            <aba number="36" usefulness="useful">
                <abaa>text1</abaa>
                <abab>
                    <ababa>text2</ababa>
                </abab>
            </aba>
            <abb number="37" usefulness="useful">
                <abba>text3</abba>
                <abbb>
                    <abbba>text4</abbba>
                    <abbbb>text5</abbbb>
                </abbb>
            </abb>
        </ab>
    </a>
</root>

生成正确的结果(请注意, Strahler number 的官方定义会将1赋予一片叶子节点 - 不是0):

<root field="4" strahler="3">
      <a strahler="3">
            <aa x="1" strahler="1"/>
            <ab strahler="3">
                  <aba number="36" usefulness="useful" strahler="2">
                        <abaa strahler="1">text1</abaa>
                        <abab strahler="1">
                              <ababa strahler="1">text2</ababa>
                        </abab>
                  </aba>
                  <abb number="37" usefulness="useful" strahler="2">
                        <abba strahler="1">text3</abba>
                        <abbb strahler="2">
                              <abbba strahler="1">text4</abbba>
                              <abbbb strahler="1">text5</abbbb>
                        </abbb>
                  </abb>
            </ab>
      </a>
</root>

使用XSLT 3.0 / XPath 3.0的更紧凑,更高效的解决方案

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="my:f">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

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

    <xsl:template match="*[true()]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:attribute name="strahler" select="f:strahler(.)"/>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

    <xsl:function name="f:strahler" as="xs:integer" cache="full">
        <xsl:param name="pElem" as="element()"/>

        <xsl:sequence select=
         "let $children := $pElem/*
           return
              if(not($children))
                then 1
                else 
                  if(not($children[2]))
                    then f:strahler($children[1])
                    else 
                      let $childrenStrahler := $children/f:strahler(.),
                          $maxVal := max($childrenStrahler),
                          $twoWithMax := ($childrenStrahler[. eq $maxVal][2]!1, 0)[1]
                        return $maxVal +$twoWithMax 
            "/>
    </xsl:function>
</xsl:stylesheet>

<强>效率

  

XSLT可能不是最佳工具......

如果有人告诉你,不要相信他们。

通过使用记忆,两种呈现的解决方案都是高效的。

事实上,f:stahler($aNode)只为每个不同的元素计算一次。

请注意使用:

  1. saxon:memo-function="yes"
  2. cache="full"
  3. 第一个是Saxon's extension attributes之一。根据{{​​3}}的文档:

      

    指定“是”表示撒克逊人应该记住结果   在缓存中调用该函数,以及是否再次调用该函数   使用相同的参数,结果将从缓存中检索   而不是重新计算。

    第二个是 saxon:memo-function

      

    值cache =“full”鼓励处理器保留内存   以前在同一转换过程中调用此函数的所有函数   尽可能重用此内存中的结果。价值   cache =“partial”鼓励处理器保留这样的内存但是   如果需要,丢弃结果以保持其中使用的内存量   界限。默认值cache =“no”鼓励处理器不要   保留以前通话的记忆。

答案 1 :(得分:1)

XSLT适合这项工作,而Dimitre的解决方案非常完美(+1)。

以下是XQuery 1.0中的替代实现:

declare function local:strahler($nodes as node()*) as node()*
{
  for $node in $nodes
  return
    typeswitch ($node)
    case document-node() return
      document {local:strahler($node/node())}
    case element() return
      element {node-name($node)}
      {
        let $children := local:strahler($node/node())
        let $max := (max($children/@strahler), 1)[1]
        return
        (
          $node/@*,
          attribute strahler {$max + (($children/@strahler[. = $max])[2]/1, 0)[1]},
          $children
        )
      }
    default return
      $node
};

local:strahler(.)

当使用strahler.xq中的输入放入文件sample.xml时,可以使用Saxon使用此命令从命令行运行它:

java net.sf.saxon.Query strahler.xq -s:sample.xml

与XSLT变体类似,它在自下而上添加新属性的同时重写文档。这里的重写比使用XSLT更明显,但在这种情况下,这是非常充分的。

答案 2 :(得分:0)

XSLT可能不是最佳工具,主要是因为它无法从下往上真正遍历树。 可以将Strahler数一次添加到树中一层,从最深层开始 - 但每次都必须重新处理整个树,导致算法非常低效。

但是,如果没有其他方法可用,你可以尝试这样的事情:

XSLT 2.0

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:variable name="max-depth" select="max(//*/count(ancestor::node()))"/>

<xsl:template match="/">
    <xsl:call-template name="strahler">
        <xsl:with-param name="node-set" select="*"/>
        <xsl:with-param name="current-level" select="$max-depth"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="strahler">
    <xsl:param name="node-set" select="*"/>
    <xsl:param name="current-level"/>

    <xsl:variable name="next-set">
        <xsl:apply-templates select="$node-set" mode="strahler">
            <xsl:with-param name="current-level" select="$current-level"/>
        </xsl:apply-templates>
    </xsl:variable>

    <xsl:choose>
        <xsl:when test="$current-level > 1">
            <!-- recursive call -->
            <xsl:call-template name="strahler">
                <xsl:with-param name="node-set" select="$next-set"/>
                <xsl:with-param name="current-level" select="$current-level - 1"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:copy-of select="$next-set"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="*" mode="strahler">
    <xsl:param name="current-level"/>
    <xsl:copy>
        <xsl:if test="count(ancestor::node()) = $current-level">
            <xsl:attribute name="strahler">
                <xsl:variable name="max-strahler" select="max(*/@strahler)" />
                <xsl:choose>
                    <xsl:when test="not(*)">
                        <xsl:value-of select="1"/>
                    </xsl:when>
                    <xsl:when test="count(*[@strahler=$max-strahler]) > 1">
                        <xsl:value-of select="$max-strahler + 1"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$max-strahler"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </xsl:if>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates mode="strahler">
            <xsl:with-param name="current-level" select="$current-level"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

应用于您的输入示例,结果将是:

<?xml version="1.0" encoding="UTF-8"?>
<root strahler="3" field="4">
   <a strahler="3">
      <aa strahler="1" x="1"/>
      <ab strahler="3">
        <aba strahler="2" number="36" usefulness="useful">
            <abaa strahler="1">text1</abaa>
            <abab strahler="1">
                <ababa strahler="1">text2</ababa>
            </abab>
        </aba>
        <abb strahler="2" number="37" usefulness="useful">
            <abba strahler="1">text3</abba>
            <abbb strahler="2">
                <abbba strahler="1">text4</abbba>
                <abbbb strahler="1">text5</abbbb>
            </abbb>
        </abb>
      </ab>
   </a>
</root>

这与您发布的结果不同,但我认为根据您链接到的维基百科文章中的规则,这是正确的。

注意&#34;身高&#34;在这里没用。