XPath/XQuery merge function

Summary

Returns a map that combines the entries from a number of existing maps.

Signatures

map:merge(
$maps as map(*)*
) as map(*)
map:merge(
$maps as map(*)*,
$options as map(*)
) as map(*)

Properties

This function is deterministic, context-independent, and focus-independent.

Rules

The function map:merge returns a map that is formed by combining the contents of the maps supplied in the $maps argument.

Informally, the supplied maps are combined as follows:

  1. There is one entry in the returned map for each distinct key present in the union of the input maps, where two keys are distinct if they are not the same key .

  2. If there are duplicate keys, that is, if two or more maps contain entries having the same key, then the way this is handled is controlled by the second ($options) argument.

The definitive specification is as follows.

  1. The effect of calling the single-argument function is the same as the effect of calling the two-argument function with an empty map as the value of $options.

  2. The $options argument can be used to control the way in which duplicate keys are handled. The option parameter conventions apply.

  3. The entries that may appear in the $options map are as follows:

    Determines the policy for handling duplicate keys: specifically, the action to be taken if two maps in the input sequence $maps contain entries with key values K1 and K2 where K1 and K2 are the same key. xs:string use-first An error is raised if duplicate keys are encountered. If duplicate keys are present, all but the first of a set of duplicates are ignored, where the ordering is based on the order of maps in the $maps argument. If duplicate keys are present, all but the last of a set of duplicates are ignored, where the ordering is based on the order of maps in the $maps argument. If duplicate keys are present, all but one of a set of duplicates are ignored, and it is implementation-dependent which one is retained. If duplicate keys are present, the result map includes an entry for the key whose associated value is the sequence-concatenation of all the values associated with the key, retaining order based on the order of maps in the $maps argument. The key value in the result map that corresponds to such a set of duplicates must be the same key as each of the duplicates, but it is otherwise unconstrained: for example if the duplicate keys are xs:byte(1) and xs:short(1), the key in the result could legitimately be xs:long(1).

The result of the function call map:merge($MAPS, $OPTIONS) is defined to be consistent with the result of the expression:

let $FOJS0003 := QName("http://www.w3.org/2005/xqt-errors", "FOJS0003"),

$duplicates-handler := map {
  "use-first":   function($a, $b) {$a},
  "use-last":    function($a, $b) {$b},
  "combine":     function($a, $b) {$a, $b},
  "reject":      function($a, $b) {fn:error($FOJS0003)},
  "use-any": function($a, $b) {fn:random-number-generator()?permute(($a, $b))[1]}
},

$combine-maps := function($A as map(*), $B as map(*), $deduplicator as function(*)) {
    fn:fold-left(map:keys($B), $A, function($z, $k){ 
        if (map:contains($z, $k))
        then map:put($z, $k, $deduplicator($z($k), $B($k)))
        else map:put($z, $k, $B($k))
    })
}
return fn:fold-left($MAPS, map{}, 
    $combine-maps(?, ?, $duplicates-handler(($OPTIONS?duplicates, "use-first")[1]))
            
            

By way of explanation, $combine-maps is a function that combines two maps by iterating over the keys of the second map, adding each key and its corresponding value to the first map as it proceeds. The second call of fn:fold-left in the return clause then iterates over the maps supplied in the call to map:merge, accumulating a single map that absorbs successive maps in the input sequence by calling $combine-maps.

This algorithm processes the supplied maps in a defined order, but processes the keys within each map in implementation-dependent order.

The use of fn:random-number-generator represents one possible conformant implementation for "duplicates":"use-any", but it is not the only conformant implementation and is not intended to be a realistic implementation. The purpose of this option is to allow the implementation to use whatever strategy is most efficient; for example, if the input maps are processed in parallel, then specifying "duplicates":"use-any" means that the implementation does not need to keep track of the original order of the sequence of input maps.

Examples

let $week := map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}

The expression map:merge(()) returns map{}.

The expression map:merge((map:entry(0, "no"), map:entry(1, "yes"))) returns map{0:"no", 1:"yes"}.

The expression map:merge(($week, map{7:"Unbekannt"})) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag", 7:"Unbekannt"}.

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"use-last"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Sonnabend"}.

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"use-first"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:"Samstag"}.

The expression map:merge(($week, map{6:"Sonnabend"}), map{"duplicates":"combine"}) returns map{0:"Sonntag", 1:"Montag", 2:"Dienstag", 3:"Mittwoch", 4:"Donnerstag", 5:"Freitag", 6:("Samstag", "Sonnabend")}.

Error Conditions

An error is raised if the value of $options indicates that duplicates are to be rejected, and a duplicate key is encountered.

An error is raised if the value of $options includes an entry whose key is defined in this specification, and whose value is not a permitted value for that key.

Notes

If the input is an empty sequence, the result is an empty map.

If the input is a sequence of length one, the result map is indistinguishable from the supplied map.

There is no requirement that the supplied input maps should have the same or compatible types. The type of a map (for example map(xs:integer, xs:string)) is descriptive of the entries it currently contains, but is not a constraint on how the map may be combined with other maps.