Как удалить дубликаты узлов в XQuery?
У меня есть 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 ответов:
Более простое и прямое однострочное решение 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
Выберите только один из его случаев - такой, что его индекс во всех книгах совпадает с первым индексом из егоtitle
в всех названий .
Решение, вдохновленное функциональным программированием. Это решение является расширяемым в том смысле, что вы можете заменить "=" сравнение с помощью вашей пользовательской булевой
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)
Я решил свою проблему, реализовав рекурсивную функцию поиска уникальности, основанную исключительно на текстовом содержании моего документа для сопоставления уникальности.
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>
Я думаю, что если вам нужно немного другое соответствие эквивалентности, вы можете соответствующим образом изменить соответствие в алгоритме. Во всяком случае, это должно помочь тебе начать.
Для удаления дубликатов я обычно использую вспомогательную функцию. В вашем случае это будет выглядеть так:
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> };