xpathxpath-3.1

XPath: How to sort a map?


I'm using a map for counting the number occurrences of, for example, each possible value of the attr attribute of the elem nodes:

<root>
    <elem attr="a"/>
    <elem attr="b"/>
    <elem attr="b"/>
    <elem         />
    <elem attr="a"/>
    <elem attr="c"/>
    <elem attr="b"/>
</root>
fold-left(
    //elem/@attr,
    map{},
    function($m,$a) {map:put($m, $a, sum((1, $m($a))))}
)

Resulting map:

{
  "a": 2,
  "b": 3,
  "c": 1
}

Now, using this map, I would like to sort the integer values in descending order and emit their associated key. The expected "output" would be:

b
a
c

How can I do it?


Solution

  • If you store the map in a variable then it's possible to call fn:sort on the keys while using the associated values as "sort keys":

    let
        $map := map{ "a": 2, "b": 3, "c": 1 }
    return
        $map => map:keys() => sort((), function($key) { -$map($key) })
    (:
        $map => map:keys() => sort((), $map) => reverse()
    :)
    
    
    b
    a
    c
    

    ASIDE

    You can also define a map:sort function for maps, which would return a sequence of keys:

    function(
        $map       as map(*),
        $collation as xs:string?,
        $valuate   as function(xs:anyAtomicType, item()*) as xs:anyAtomicType*
    ) as xs:anyAtomicType*
    {
        sort(
            map:keys($map),
            $collation,
            function($key) { $valuate($key, $map($key)) }
        )
    }
    
    let $map:sort := function (...) {...}
    return
        map{ "a": 2, "b": 3, "c": 1 }
        => $map:sort((), function($k,$v) { -$v })