Как я могу удалить дубликаты узлов в XQuery? - PullRequest
16 голосов
/ 13 марта 2009

У меня есть 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>

Мне нужна только одна запись в корневом теге моих книг, и есть другие теги, такие как, скажем, брошюра, которые также должны удалить дубликаты. Есть идеи?


Обновлено следующие комментарии. Под уникальными узлами я подразумеваю удаление нескольких экземпляров узлов, имеющих одинаковое содержимое и структуру.

Ответы [ 7 ]

16 голосов
/ 20 марта 2009

Более простое и прямое однострочное решение 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 в всех заголовках .

5 голосов
/ 11 марта 2010

Вы можете использовать встроенную функцию distinct-values() ...

2 голосов
/ 01 июля 2010

Решение, основанное на функциональном программировании. Это решение является расширяемым, поскольку вы можете заменить "=" сравнением на пользовательскую функцию 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)
1 голос
/ 13 апреля 2017

Для удаления дубликатов я обычно использую вспомогательную функцию. В вашем случае это будет выглядеть так:

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>
};
1 голос
/ 29 мая 2010

А как насчет fn: Different-values?

1 голос
/ 14 марта 2009

Я решил свою проблему, реализовав рекурсивную функцию поиска уникальности, основанную исключительно на текстовом содержимом моего документа для сопоставления уникальности.

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>

Полагаю, если вам нужно немного отличающееся сопоставление эквивалентности, вы можете соответствующим образом изменить сопоставление в алгоритме. В любом случае, вам следует начать.

0 голосов
/ 27 ноября 2014

Вы можете использовать эту функцию functx: functx: Different-deep

Не нужно изобретать велосипед

...