Reputation: 373
I have a database that look like this (access it via $database
):
<country car_code="F" area="547030" capital="cty-france-paris">
<name>France</name>
<border country="AND" length="60"/>
<border country="E" length="623"/>
<border country="D" length="451"/>
<border country="I" length="488"/>
<border country="CH" length="573"/>
<border country="B" length="620"/>
<border country="L" length="73"/>
<border country="MC" length="4.4"/>
</country>
.....
other countries
I would like to write a function that gives the names of all countries reachable from France (or any other country) via land borders. A first attempt (probably with plenty of syntax errors and other errors, but the semantics of the program should be "more clear"):
declare function local:reachable($country as element())
as (return value should be a sequence of countries )
{
if $country == () (:if empty, it doesn't border to any other country:)
then ()
else(
$country/name UNION (for $bord in $country/border/@country return
local:reachable ($database/country/car_code = @bord ))
)
}
The call to that function:
local:reachable($database/country[@car_code = "F"])
The bordering countries to France should be:
<border country="AND" length="60"/>
<border country="E" length="623"/>
<border country="D" length="451"/>
<border country="I" length="488"/>
<border country="CH" length="573"/>
<border country="B" length="620"/>
<border country="L" length="73"/>
<border country="MC" length="4.4"/>
But we also need to find the bordering countries for these countries. the final output should be "F", "AND", "E", "D", "I", "CH", "B", "L", "MC"..., X, Y, Z, (and other countries that border to these countries).
I know UNION is not defined but is there anything else I can use? I just wanted it to be more clear to what I want to do
One big problem, other than the syntax errors, is that if "F" borders to "L" then "L" will border with "F" so my "function" will never terminate - how can I handle that?
Could I get some help with the syntax
If the question is not clear please let me know so that i can clarify it further
Upvotes: 3
Views: 2156
Reputation: 2327
Here are a few comments on your code:
$country as element()
defines a variable which MUST contain
exactly one element, so it never can be empty; use element()?
if
the element is optional, element()*
if there can be any number of
them, or element()+
if there must be one or more
the sequence operator ,
can be used to construct sequences from
other sequences: (1,2) , (3,4)
constructs 2 sequences: (1,2)
and
(3,4)
, then constructs another one containing all items in the
others, resulting in: (1,2,3,4)
Let me change slightly the countries
element, so I remove the noise,
and make it a bit simpler for this demonstration. Also, I create a
simple, yet complete map. Let us say we have 2 adjacent countries U
and K, and 4 others forming a square (each country is neighbourgh to 2
others): N, G, B, and F. Any similarity to existing geography or
politics is only in your eyes :-)
<!--
Map: U K | N G
B F
-->
<countries>
<country id="U">
<name>Over the top</name>
<border idref="K"/>
</country>
<country id="K">
<name>Beyond the see</name>
<border idref="U"/>
</country>
<country id="N">
<name>Flatland</name>
<border idref="B"/>
<border idref="G"/>
</country>
<country id="G">
<name>Marxhome</name>
<border idref="N"/>
<border idref="F"/>
</country>
<country id="B">
<name>Beerium</name>
<border idref="N"/>
<border idref="F"/>
</country>
<country id="F">
<name>Grapeandcheese</name>
<border idref="B"/>
<border idref="G"/>
</country>
</countries>
The solution includes a recursive function, that consumes a queue of countries to handle. Meanwhile, it accumulates the result list one country at a time. It takes the first country in the queue, add it to the result, then recurse on all adjacent countries which are not already in the queue nor the current result. The augmented result is passed down as well.
xquery version "3.0";
declare variable $countries :=
<countries>
<!-- as above, just copy and paste it -->
</countries>;
declare function local:reachable(
$queue as element(country)*,
$result as element(country)*
) as element(country)*
{
if ( empty($queue) ) then (
(: we do not consider one country reachable from itself :)
tail($result)
)
else (
let $this := head($queue)
let $rest := tail($queue)
let $more := $this/border/@idref[not(. = ($queue, $result)/@id)]
return
local:reachable(
( $rest, $countries/country[@id = $more] ),
( $result, $this ))
)
};
(: for each countries, display its reachable countries
:)
for $c in $countries/country
order by $c/@id
let $r := local:reachable($c, ())
return
$c/name || ': ' || string-join($r/@id, ', ')
Beerium: N, G, F
Grapeandcheese: N, G, B
Marxhome: N, B, F
Beyond the see: U
Flatland: G, B, F
Over the top: K
Upvotes: 6