Как обрабатывать рекурсию в XQuery?

Я пытаюсь найти все страны, до которых можно добраться по суше, перемещаясь из одной страны в другую через сухопутные границы, используя базу данных mondial.sql. Это должно быть сделано рекурсивно, и я нашел в Интернете некоторые функции, которые, как я подумал, были бы полезны для объединения последовательностей и для исключения стран, которые уже были найдены.

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

(:functx.value-union and is-value-in-sequence were found at http://www.xqueryfunctions.com/xq/:)
declare namespace functx = "http://www.functx.com";
declare function functx:value-union
  ( $arg1 as xs:anyAtomicType* ,
    $arg2 as xs:anyAtomicType* )  as xs:anyAtomicType* {

  distinct-values(($arg1, $arg2))
 };

 declare function functx:is-value-in-sequence
  ( $value as xs:anyAtomicType? ,
    $seq as xs:anyAtomicType* )  as xs:boolean {

   $value = $seq
 } ;

(:Recursive function for finding reachable countries:)
declare function local:findReachable($countries, $country, $reachedCountries) {
    let $reachableCountries := $countries[@car_code = $country/border/@country]
    for $c in $reachableCountries
    where not(functx:is-value-in-sequence($c, $reachedCountries))
    return functx:value-union($c, local:findReachable($countries, $c, functx:value-union($reachableCountries, 
    $reachedCountries)))
};

let $countries := //country
let $startingCountry := //country[@car_code = 'S']
return local:findReachable($countries, $startingCountry, $startingCountry)

person ChristofferAB    schedule 07.05.2017    source источник


Ответы (1)


Ваши чеки с $reachedCountries только гарантируют, что страны не появятся дважды на одном и том же пути, но вы по-прежнему посещаете каждую страну на всех возможных путях, что занимает много времени. Нет цикла, просто избыточность.

Вот простой поиск в глубину, который делает то, что вы хотите:

declare function local:dfs($stack, $seen) {
  if(empty($stack)) then $seen
  else (
    let $country := $stack[1]
    let $neighbors :=
        for $code in $country/border/@country[not(. = $seen/@car_code)]
        return $country/../country[@car_code = $code]
    return local:dfs(($neighbors, $stack[position() > 1]), ($seen, $neighbors))
  )
};

local:dfs(doc('mondial.xml')//country[@car_code = 'S'], ())/name
person Leo Wörteler    schedule 07.05.2017