【问题标题】:How to count the number of child nodes in a directed acyclic graph with xsl如何用xsl计算有向无环图中的子节点数
【发布时间】:2018-04-08 12:12:02
【问题描述】:

我已经简化了一个表示有向无环图的 xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<items>
  <item id="1">
  </item>
  <item id="2">
    <parent idref="1" />
  </item>
  <item id="3">
    <parent idref="1" />
  </item>
  <item id="4">
    <parent idref="3" />
  </item>
  <item id="5">
    <parent idref="4" />
  </item>
  <item id="6">
    <parent idref="3" />
  </item>
  <item id="7">
    <parent idref="4" />
  </item>
  <item id="8">
    <parent idref="4" />
  </item>
  <item id="9">
    <parent idref="4" />
  </item>
  <item id="10">
    <parent idref="6" />
  </item>
</items>

这种表示允许无限深度(我不知道数学术语)。除了没有父项的根项之外,每个项都有一个且只有一个父项。每个项目可以有 0 到任意数量的子项目。每个项目都由它的 id 标识,它是一个任意标签。

用点表示法可视化图形可能更容易。在我的文件中,更容易看到第 3 项有两个子节点,第 4 项有 4 个:

digraph
{
    1 -> {2, 3}
    3 -> {4, 6}
    4 -> {5, 7, 8, 9}
    6 -> 10
}

计算第 3 项的子项数为:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/">
    <xsl:value-of select="count(items/item/parent[@idref='3'])" />
</xsl:template>

</xsl:stylesheet>

xslt 样式表输出 2,这是正确的。如果我把3改成4,那么输出就是4,也是正确的。

问题:我想计算特定项目的所有子项目。

对于第 3 项,正确答案是 7(第 4、6、5、7、8、9、10 项)。对于第 1 项,答案是 9。对于第 7 项,答案是 0。我怀疑答案涉及递归,我已经成功地使用它来构造树的各个部分,但没有传递值或计算总和。

【问题讨论】:

    标签: xml xslt directed-acyclic-graphs


    【解决方案1】:

    对于以下交叉引用,我建议设置键,然后确实,为了解决问题,递归是解决方案的(另一个)关键,因此在 XSLT 2 或 3 中,您可以使用递归函数来做到这一点:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="xs mf"
        version="3.0">
    
      <xsl:param name="start-id">7</xsl:param>
    
      <xsl:key name="id" match="item" use="@id"/>
      <xsl:key name="child" match="item" use="parent/@idref"/>
    
      <xsl:function name="mf:descendants" as="element(item)*">
          <xsl:param name="item" as="element(item)*"/>
          <xsl:sequence 
            select="let $children := key('child', $item/@id, root($item))
                    return ($children | $children!mf:descendants(.))"/>
      </xsl:function>
    
      <xsl:template match="/">
          <xsl:value-of select="count(mf:descendants(key('id', $start-id)))"/>
      </xsl:template>
    
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/bdxtpH/0https://xsltfiddle.liberty-development.net/bdxtpH/1https://xsltfiddle.liberty-development.net/bdxtpH/2 是一些示例。

    对于 XSLT 2,您将使用本地 xsl:variable 而不是上面使用的 let $children

    <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="xs mf">
    
      <xsl:param name="start-id">1</xsl:param>
    
      <xsl:key name="id" match="item" use="@id"/>
      <xsl:key name="child" match="item" use="parent/@idref"/>
    
      <xsl:function name="mf:descendants" as="element(item)*">
          <xsl:param name="item" as="element(item)*"/>
          <xsl:variable name="children" select="key('child', $item/@id, root($item))"/>
          <xsl:sequence 
            select="$children | $children/mf:descendants(.)"/>
      </xsl:function>
    
      <xsl:template match="/">
          <xsl:value-of select="count(mf:descendants(key('id', $start-id)))"/>
      </xsl:template>
    
    </xsl:transform>
    

    http://xsltransform.hikmatu.com/pPgCcou

    对于 XSLT 1,您可以使用稍微不同的方法,使用递归命名模板收集后代,直到找不到更多子代,然后输出计数:

    <xsl:stylesheet
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">
    
        <xsl:param name="start-id">7</xsl:param>
    
        <xsl:key name="id" match="item" use="@id"/>
        <xsl:key name="child" match="item" use="parent/@idref"/>
    
        <xsl:template name="count-descendants">
            <xsl:param name="descendants" select="/.."/>
            <xsl:param name="level"/>
            <xsl:variable name="children" select="key('child', $level/@id)"/>
            <xsl:choose>
                <xsl:when test="not($children)">
                    <xsl:value-of select="count($descendants)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:call-template name="count-descendants">
                        <xsl:with-param name="descendants" select="$descendants | $children"/>
                        <xsl:with-param name="level" select="$children"/>
                    </xsl:call-template>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    
        <xsl:template match="/">
            <xsl:variable name="start-item" select="key('id', $start-id)"/>
            <xsl:call-template name="count-descendants">
                <xsl:with-param name="level" select="$start-item"/>
            </xsl:call-template>
        </xsl:template>
    
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/3NzcBsK/0https://xsltfiddle.liberty-development.net/3NzcBsK/1https://xsltfiddle.liberty-development.net/3NzcBsK/2有样本数据。

    【讨论】:

    • 由于我想将此解决方案合并到 PHP 中,因此 XSLT 2.0 和 3.0 超出了范围。但是 1.0 版本好像可以用了!
    • 我已将 xslt 1.0 解决方案合并到我的 PHP 代码中,并且运行速度非常快。我拥有的 DAG 有 300 多个节点,大约 40 深。我以为它会很慢,但事实并非如此。这是一个很好的解决方案,再多思考一下我就会明白。谢谢,马丁。
    【解决方案2】:

    为了多样化,这里有另一种方法:

    <xsl:param name="start-id">7</xsl:param>
    
    <xsl:key name="id" match="item" use="@id"/>
    
    <xsl:function name="f:has-ancestor" as="xs:boolean">
      <xsl:param name="d" as="item"/>
      <xsl:param name="a" as="item"/>
      <xsl:sequence select="exists($d) and ($a is $d or 
           f:has-ancestor(key('id', $d/parent/@idref), $a))"/>
    </xsl:function>
    

    然后

    select="count(//item[f:has-ancestor(., key('id', $start-id))])"/>
    

    (当然,哪个更快取决于你的树的茂密程度。)

    【讨论】:

    • 我不能使用这个解决方案,因为它涉及 XSLT 2.0 而 PHP 没有使用它。但是得到 The Michael Kay 的回复意味着我已经被 XML 的伟大所感动。您的书 XSLT 第 2 版对我来说是非常宝贵的参考。
    • Saxon/C 提供了从 PHP 调用 XSLT 3.0 的能力。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-23
    相关资源
    最近更新 更多