如何删除 XQuery 中的重复节点?

Posted

技术标签:

【中文标题】如何删除 XQuery 中的重复节点?【英文标题】:How can I remove duplicate nodes in XQuery? 【发布时间】:2010-10-13 06:19:26 【问题描述】:

我有一个即时生成的 XML 文档,我需要一个函数来消除其中的任何重复节点。

我的函数看起来像:

declare function local:start2() 
    let $data := local:scan_books()
    return <books>$data</books>
;

示例输出为:

<books>
  <book>
    <title>XML in 24 hours</title>
    <author>Some Guy</author>  
  </book>
  <book>
    <title>XML in 24 hours</title>
    <author>Some Guy</author>  
  </book>
</books>

我只想要我的书根标签中的一个条目,还有其他标签,比如那里的小册子也需要删除重复项。有什么想法吗?


更新了以下 cmets。我所说的唯一节点是指删除多次出现的具有完全相同内容和结构的节点。

【问题讨论】:

【参考方案1】:

为了删除重复项,我通常使用辅助函数。在你的情况下,它看起来像这样:

declare function local:remove-duplicates($items as item()*) 
as item()*

  for $i in $items
  group by $i
    return $items[index-of($items, $i)[1]]
;

declare function local:start2() 
    let $data := local:scan_books()
    return <books>local:remove-duplicates($data)</books>
;

【讨论】:

【参考方案2】:

受函数式编程启发的解决方案。这个解决方案是可扩展的,您可以用 custom-built boolean local:compare($element1, $element2) 函数替换 "=" 比较。此函数在列表长度上具有最坏情况二次复杂度。您可以通过预先对列表进行排序并仅与直接后继者进行比较来获得n(log n) 复杂性。

据我所知,fn:distinct-values(或fn:distinct-elements)函数不允许使用自定义比较函数。

declare function local:deduplicate($list) 
  if (fn:empty($list)) then ()
  else 
    let $head := $list[1],
      $tail := $list[position() > 1]
    return
      if (fn:exists($tail[ . = $head ])) then local:deduplicate($tail)
      else ($head, local:deduplicate($tail))
;

let $list := (1,2,3,4,1,2,1) return local:deduplicate($list)

【讨论】:

这个解决方案似乎有效。你能解释一下“fn:exists($tail[ . = $head ])”这行吗?我已将其修改为“$head = $tail”,并且可以正常工作。【参考方案3】:

更简单、更直接的单行 XPath 解决方案

只需使用以下 XPath 表达式

  /*/book
        [index-of(/*/book/title, 
                  title
                 )
                  [1]
        ]

应用时,例如,在以下 XML 文档上

<books>
    <book>
        <title>XML in 24 hours</title>
        <author>Some Guy</author>
    </book>
    <book>
        <title>Food in Seattle</title>
        <author>Some Guy2</author>
    </book>
    <book>
        <title>XML in 24 hours</title>
        <author>Some Guy</author>
    </book>
    <book>
        <title>Food in Seattle</title>
        <author>Some Guy2</author>
    </book>
    <book>
        <title>How to solve XPAth Problems</title>
        <author>Me</author>
    </book>
</books>

上述 XPath 表达式正确选择了以下节点

<book>
    <title>XML in 24 hours</title>
    <author>Some Guy</author>
</book>
<book>
    <title>Food in Seattle</title>
    <author>Some Guy2</author>
</book>
<book>
    <title>How to solve XPAth Problems</title>
    <author>Me</author>
</book>

解释很简单:对于每一个book,只选择它出现的一个——这样它在all-books中的索引与第一个相同所有标题中其title 的索引。

【讨论】:

嘿Dimitre,谢谢你的回答;但如果我理解正确,这取决于查询中内置的具有相同结构的所有元素 - 例如,如果它们具有相同的标题和不同的作者,它将显示两个相同的节点...... @Brabster 您的问题完全不清楚应该如何定义不平等/唯一性测试。如果你定义它,它将帮助你找到一个更简单的解决方案 这似乎不适用于 XPath 1.0,我们可以得到一个有效的 XPath 1.0 解决方案吗? @Abarax:这个问题被标记为“xquery”。 XQuery 是 XPAth 2.0 的超集。从来没有人要求 XPath 1.0 的答案。这种操作通常称为grouping,一般情况下不能用单个XPath 表达式来表达,而在特定情况下,当存在这样的单个XPath 1.0 表达式时,它可能效率低下。这就是为什么 XSLT 1.0 专门用于高效分组的原因——例如。 Muenchian 分组方法。 @Abarax:类似的表达式也可以——需要指定字段的连接。【参考方案4】:

你可以使用这个functx函数:functx:distinct-deep

无需重新发明***

【讨论】:

【参考方案5】:

你可以使用内置的distinct-values()函数...

【讨论】:

你怎么用它?【参考方案6】:

fn:distinct-values 呢?

【讨论】:

【参考方案7】:

我通过实现递归唯一性搜索功能解决了我的问题,该功能仅基于文档的文本内容进行唯一性匹配。

declare function ssd:unique-elements($list, $rules, $unique) 
    let $element := subsequence($rules, 1, 1)
    let $return :=
    if ($element) then
        if (index-of($list, $element) >= 1) then
            ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), $unique)
        else <test>
            <unique>$element</unique>
            ssd:unique-elements(insert-before($element, 1, $list), subsequence($rules, 2), insert-before($element, 1, $unique))/*
            </test>
    else ()
    return $return
;

调用如下:

declare function ssd:start2() 
    let $data := ()
    let $sift-this := 
       <test>
           <data>123</data>
           <data>456</data>
           <data>123</data>
           <data>456</data>
           <more-data>456</more-data>
       </test>
    return ssd:unique-elements($data, $sift-this/*, ())/*/*
;

ssd:start2()

输出:

<?xml version="1.0" encoding="UTF-8"?>
<data>123</data>
<data>456</data>

我想如果你需要稍微不同的等价匹配,你可以相应地改变算法中的匹配。无论如何应该让你开始。

【讨论】:

以上是关于如何删除 XQuery 中的重复节点?的主要内容,如果未能解决你的问题,请参考以下文章

xQuery,如何从带有孩子的节点获取文本?

XQuery(或 XPath)是不是具有更新、插入和删除以及选择的等价物?

如何使用密码删除两个节点之间的重复关系?

如何在 PHP 中删除 XML 的节点 [重复]

如何删除链表中值重复的节点

如何根据条件从 XML 中删除节点?