How to handle recursion in XQuery?

牧云@^-^@ 提交于 2019-12-04 13:24:23

问题


I'm trying to find all countries which are reachable by land by traversing from one country to another via land borders using the mondial.sql database. It must be done recursively and I found some functions online which I thought would be useful for joining sequences and to be able to exclude countries which have already been found.

The problem is I end up in a loop even though the countries that are to be excluded seems to be handled properly. So my thinking is that I might have to define a base case in some way to make the recursion stop once all possible countries have been found. How to achieve this with 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)

回答1:


Your checks with $reachedCountries only guarantee that countries do not appear twice on the same path, but you still visit every country along every possible path, which takes a long time. There is no loop, just lots of redundancy.

Here is a simple depth-first search that does what you want:

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


来源:https://stackoverflow.com/questions/43831040/how-to-handle-recursion-in-xquery

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!