Как удалить повторяющиеся узлы в 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>
Я хочу только одну запись в корневом теге моих книг, и есть другие теги, например, скажем, проспект, в котором нужно удалить дубликаты. Любые идеи?
Обновлены следующие комментарии. Под уникальными узлами я имею в виду удаление нескольких вхождений узлов, которые имеют одинаковое содержимое и структуру.
Ответы
Ответ 1
Более простое и более прямое однострочное решение 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
в все титры.
Ответ 2
Вы можете использовать встроенную функцию distinct-values()
...
Ответ 3
Решение, основанное на функциональном программировании. Это решение является расширяемым, поскольку вы можете заменить сравнение "=" на функцию . Эта функция имеет наихудший квадратичную сложность в длине списка. Вы можете получить сложность 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)
Ответ 4
Я решил свою проблему, реализовав функцию рекурсивного поиска уникальности, основанную исключительно на текстовом содержимом моего документа для соответствия уникальности.
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>
Я думаю, если вам нужно немного другое соответствие эквивалентности, вы можете соответствующим образом изменить соответствие в алгоритме. Если вы начнете в любом случае.
Ответ 5
Как насчет fn: distinct-values?
Ответ 6
Чтобы удалить дубликаты, я обычно использую вспомогательную функцию. В вашем случае это будет выглядеть так:
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>
};
Ответ 7
Вы можете использовать эту функцию functx: functx: distinct-deep
Не нужно изобретать колесо