質問

I am trying to manage the output of the map:get function in BaseX.

The mapping file looks as follows:

 <around>
   <point NR="51151">161</point>
   <point NR="31252">82</point>
   <point NR="54321">323</point>
   <point NR="54321">319</point>
   <point NR="54321">327</point>
 </around>

and represents some geographic points with the No. as attribute (NR) and the distance from the certain search point as value. There are some points which are present more than one time (as the point 54321 in the example above). This is content-related ok since these are "branches" of the point with the same No.

What I wand to get is the average from the distances if I lookup these NR's.

My query:

 let $c :=
      <around>
        <point NR="51151">161</point>
        <point NR="31252">82</point>
        <point NR="54321">323</point>
        <point NR="54321">319</point>
        <point NR="54321">327</point>
      </around>
 for $r in $c//point
 let $m := map { data($r/@NR) := $r/text() }
 return
 if ( map:contains($m, '54321'  ) ) then
 avg(map:get($m, '54321'))
 else
 ()

... returns 323 319 327 , thus ignores the "avg" though there is no syntax error message in BaseX.

How could I achieve the above? Many thanks in advance!

役に立ちましたか?

解決

A map associates one key with one value. What you are actually doing here is creating one single-entry map $m for every iteration of the for loop. Your code is exactly equivalent to this:

for $r in $c/point
return if ($r/@NR eq '54321') then avg($r/text()) else ()

If you are only looking up a single key, I recommend you not use a map at all and just do this, which is much simpler and faster:

return avg($c/point[@NR='54321'])

Even if you are looking up multiple keys a naive implementation that uses group by or distinct-values() may still be faster than a map if BaseX has a fresh attribute index on the document you are querying.

That said, to use a map you need to build up single new map using map:new and group by

let $c :=
      <around>
        <point NR="51151">161</point>
        <point NR="31252">82</point>
        <point NR="54321">323</point>
        <point NR="54321">319</point>
        <point NR="54321">327</point>
      </around>
let $m := map:new(
    for $r in $c/point
    let $key := $r/@NR, $value := $r/text()
    group by $key
    return map {$key := $value}
) 
return avg($m('54321'))

You can also do this using function reduction (the fold- functions) instead of group-by:

let $c :=
      <around>
        <point NR="51151">161</point>
        <point NR="31252">82</point>
        <point NR="54321">323</point>
        <point NR="54321">319</point>
        <point NR="54321">327</point>
      </around>
let $reducer := function($points as map(*), $point as item()) {
    map:new((
      $points, 
      map { $point/@NR := ($points($point/@NR), $point/text())}
    ))
}
let $m := fold-left($reducer, map{}, $c/point)
return avg($m('54321'))

Remember that in production code you need to generate the map once and then use your list of key lookups on it to have any hope of seeing a speed benefit from using a map.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top