Search code examples
xpathxsltrandomsaxonxslt-3.0

Generate a sequence of random numbers


I am trying to generate a sequence of random numbers using the XPath 3.1 random-number-generator() function.

The specification states that the function returns a map containing three entries, and that the entry with key "next" is a zero-arity function that can be called to return another random number generator. However, there are no instructions (at least not instructions that I could understand) regarding how exactly to do that.

I was able, after some trial and error, to produce two different random numbers using:

<test>
    <xsl:value-of select="random-number-generator()?number" />
    <xsl:text> - </xsl:text>
    <xsl:value-of select="random-number-generator()?next()?number" />
</test>

However, adding a third call using the same code as the second one:

<test>
    <xsl:value-of select="random-number-generator()?number" />
    <xsl:text> - </xsl:text>
    <xsl:value-of select="random-number-generator()?next()?number" />
    <xsl:text> - </xsl:text>
    <xsl:value-of select="random-number-generator()?next()?number" />
</test>

results in the last two random numbers being equal, for example:

<test>0.8258447548324238 - 0.37162622506875487 - 0.37162622506875487</test>

(full test at https://xsltfiddle.liberty-development.net/gWmsLza).

The specification also provides an example of "a function that can be called to produce a random sequence of xs:double values in the range zero to one, of specified length". This is what I would eventually like to do, but the provided code is not in the XSLT language and I am not able to understand how it works.

Can someone please provide clear instructions how to call the function repeatedly so that it provides a new random number at each call?


Update:

What I understand from the responses so far is that I need to use the next() instruction repeatedly on the same random number generator.

So that my basic example should look something like:

<xsl:variable name="rng" select="random-number-generator()"/>
<test>
    <xsl:value-of select="$rng?number" />
    <xsl:text> - </xsl:text>
    <xsl:value-of select="$rng?next()?number" />
    <xsl:text> - </xsl:text>
    <xsl:value-of select="$rng?next()?next()?number" />
</test>

Solution

  • To transcribe the XQuery code in the spec example to XSLT 3.0:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="#all"
        expand-text="yes"
        version="3.0">
      
      <xsl:function name="mf:random-sequence" as="xs:double*">
        <xsl:param name="length" as="xs:integer"/>
        <xsl:sequence select="mf:random-sequence($length, random-number-generator())"/>
      </xsl:function>
      
      <xsl:function name="mf:random-sequence" as="xs:double*">
        <xsl:param name="length" as="xs:integer"/>
        <xsl:param name="G" as="map(xs:string, item())"/>
        <xsl:sequence
          select="if ($length eq 0)
                  then ()
                  else ($G?number, mf:random-sequence($length - 1, $G?next()))"/>
      </xsl:function>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="data">
        <xsl:copy>{mf:random-sequence(.)}</xsl:copy>
      </xsl:template>
      
    </xsl:stylesheet>
    

    Note however, that one drawback is still that the same seed is used on each function call so repeated calls can yield the same results.

    In the context of XSLT, to prevent that, I tend to use an accumulator passing the new random number generator/?next() as the value, or you can feed the generate-id of the current node as a seed to the random-number generator.

    Here is an example "passing on a new random number generator/the next() one" in an accumulator, to ensure we get a new random sequence in each node (e.g. here the data element) we need it:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="#all"
        expand-text="yes"
        version="3.0">
      
      <xsl:function name="mf:random-sequence" as="xs:double*">
        <xsl:param name="length" as="xs:integer"/>
        <xsl:param name="G" as="map(xs:string, item())"/>
        <xsl:sequence
          select="if ($length eq 0)
                  then ()
                  else ($G?number, mf:random-sequence($length - 1, $G?next()))"/>
      </xsl:function>
      
      <xsl:accumulator name="rng" as="map(xs:string, item())" initial-value="random-number-generator(current-dateTime())">
        <xsl:accumulator-rule match="data" select="$value?next()"/>
      </xsl:accumulator>
    
      <xsl:mode on-no-match="shallow-copy" use-accumulators="rng"/>
    
      <xsl:template match="data">
        <xsl:copy>{mf:random-sequence(., accumulator-before('rng'))}</xsl:copy>
      </xsl:template>
      
    </xsl:stylesheet>
    

    To see the advantage and difference, compare the results in https://xsltfiddle.liberty-development.net/nc4M3Fe/1 to https://xsltfiddle.liberty-development.net/nc4M3Fe/0.

    fold-left is also handy to create a sequence of random numbers e.g.

      <xsl:function name="mf:random-sequence" as="xs:double*">
        <xsl:param name="length" as="xs:integer"/>
        <xsl:param name="rng" as="map(xs:string, item())"/>
        <xsl:sequence
          select="fold-left(
                    1 to $length, 
                    $rng, 
                    function($a, $i) { 
                      let $rng := head($a) 
                      return ($rng?next(), $rng?number, tail($a)) 
                    }
                  ) => tail()"/>
      </xsl:function>
    

    https://xsltfiddle.liberty-development.net/nc4M3Fe/2

    As you ask in a comment for a simpler example creating three random numbers:

    let $rng1 := random-number-generator(current-dateTime()),
        $rng2 := $rng1?next(),
        $rng3 := $rng2?next()
    return ($rng1?number, $rng2?number, $rng3?number)
    

    Online XPath 3.1 fiddle.