How to handle recursion in XQuery?
basex, recursion, xml, xpath, xquery
Solution
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
Problem
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) ```