Использование fn: random-number-generator для получения случайных чисел более одного раза

Я пытаюсь написать простую функцию, которая будет выдавать мне случайную букву каждый раз, когда я ее вызываю, но мне трудно совмещать свою идею с концепцией функционального подхода к программированию. Будем признательны за некоторую помощь! Код, который у меня есть, выглядит так:

<xd:doc>
        <xd:desc>Provides one random letter, if the type is provided it returns a letter of thet type</xd:desc>
        <xd:param name="type">The type of letter to return, one of (A,a,B,b)</xd:param>
    </xd:doc>
    <xsl:function name="gdpr:randomLetter" as="xs:string">
        <xsl:param name="type" as="xs:string"></xsl:param>
        <xsl:choose>
            <xsl:when test="$type = 'A'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
                <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'a'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 7)[1]"/>
                <xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'B'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
                <xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:when test="$type = 'b'">
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 19)[1]"/>
                <xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="randomNumber" select="random-number-generator()['next']?permute(1 to 52)[1]"/>
                <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
                <xsl:value-of select="$letters[$randomNumber]"/>
            </xsl:otherwise>
        </xsl:choose>

    </xsl:function>

person Erik Zander    schedule 24.04.2019    source источник


Ответы (4)


Ваш вопрос инкапсулирует проблему:

Я пытаюсь написать простую функцию, которая будет выдавать мне случайную букву каждый раз, когда я ее вызываю.

Но функция, которая дает разные результаты при разных вызовах (с одними и теми же аргументами), не является истинной («чистой») функцией.

Один из выходов - использовать тот факт, что XSLT уже имеет своего рода «нечистые» функции: функция, которая создает новый узел, каждый раз возвращает другой узел, и вы можете раскрыть это, используя generate-id (). Чтобы вы могли написать

<xsl:function name="my:random" as="xs:double">
  <xsl:variable name="dummy"><a/></xsl:variable>
  <xsl:sequence select="fn:random-number-generator(generate-id($dummy))?permute(1 to 10)"/>
</xsl:function>

Единственная проблема заключается в том, что вы находитесь на грани того, что четко определено в спецификации, и оптимизатор может не позволить вам уйти от таких уловок. Гораздо лучше, если вы можете, найти способ передачи различных аргументов функции каждый раз, когда она вызывается: например, порядковый номер или generate-id (), применяемый к входному узлу, который вы в настоящее время обрабатываете.

person Michael Kay    schedule 25.04.2019
comment
Мне также нравится этот ответ: если вам нужны побочные эффекты в декларативной среде, вам нужно будет самостоятельно кодировать тип состояния. Дополнительное чтение stackoverflow.com/questions / 3850368 / - person Alejandro; 26.04.2019

В контексте XSLT 3 я думаю, что один из способов создать «новый» random-number-generator для каждого нужного вам узла - это определить аккумулятор:

<xsl:accumulator name="rng" as="map(xs:string, item())" initial-value="random-number-generator(current-dateTime())">
    <xsl:accumulator-rule match="foo[@type]" select="$value?next()"/>
</xsl:accumulator>

Таким образом, вы можете реализовать свою функцию как

<xsl:function name="gdpr:randomLetter" as="item()*">
    <xsl:param name="type" as="xs:string"/>
    <xsl:param name="rng" as="map(xs:string, item())"/>
    <xsl:choose>
        <xsl:when test="$type = 'A'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
            <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'a'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 7)[1]"/>
            <xsl:variable name="letters" select="('a','o','u','e','i','y','q')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'B'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
            <xsl:variable name="letters" select="('W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:when test="$type = 'b'">
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 19)[1]"/>
            <xsl:variable name="letters" select="('w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:variable name="randomNumber" select="$rng?permute(1 to 52)[1]"/>
            <xsl:variable name="letters" select="('A','O','U','E','I','Y','Q','a','o','u','e','i','y','q','w','r','t','p','s','d','f','g','h','j','k','l','m','n','b','v','c','x','z','W','R','T','P','S','D','F','G','H','J','K','L','M','N','B','V','C','X','Z')"/>
            <xsl:sequence select="$letters[$randomNumber]"/>
        </xsl:otherwise>
    </xsl:choose>        
</xsl:function>

а затем назовите его, например,

<xsl:template match="foo[@type]">
    <xsl:copy>
        <xsl:value-of select="gdpr:randomLetter(@type, accumulator-before('rng'))"/>   
    </xsl:copy>
</xsl:template>

и убедитесь, что вы используете

<xsl:mode on-no-match="shallow-copy" use-accumulators="rng"/>
person Martin Honnen    schedule 24.04.2019

Проблема в том, что fn:random-number-generator function является детерминированный. В самой спецификации объясняется, что:

Обе формы функции являются · детерминированными ·: двойной вызов функции с одними и теми же аргументами в пределах одной · области выполнения · дает одинаковые результаты.

Вам необходимо правильно использовать функцию под ключом next, содержащуюся в результирующей карте, после вызова функции random-number-generator. Как сказано в спецификации:

Запись с ключом «next» - это функция нулевой арности, которую можно вызвать для возврата другого генератора случайных чисел.

person Alejandro    schedule 24.04.2019
comment
Спасибо, Алехандро, что я прочитал и понял. Однако мне могла бы пригодиться некоторая помощь в том, как правильно использовать следующую функцию. Небольшой пример был бы полезен. - person Erik Zander; 24.04.2019
comment
@ErikZander, вам в основном нужно написать функцию, принимающую генератор случайных чисел в качестве аргумента и рекурсивно вызывающую себя со значением $rng?next(), как это сделано в спецификации в примере функции r:random-sequence или как Майкл Кей делает в комментарии к stackoverflow.com/a/41586171/252228 с fold-left: fold-left(1 to 10, random-number-generator(), function($z, $i){ head($z)?next(), tail($z), head($z)?number}), чтобы дальнейшая обработка оставалась возможной, используя head() этого вызова или остановив его с помощью tail(). - person Martin Honnen; 24.04.2019

Для полноты картины я предложил это решение, но оно работает только для небольших фрагментов текста из-за глубины рекурсии.

Кстати, я понял, что мое решение было пустой тратой времени, поскольку я использую exists-db, который не включает генератор случайных чисел в своей реализации XSLT.

<xsl:function name="gdpr:rngRecurseStart">
     <xsl:param name="text"></xsl:param>
     <xsl:variable name="chars" select="functx:chars($text)"/>

     <xsl:copy-of select="gdpr:rngRecurse($chars,random-number-generator(current-dateTime()),'')"></xsl:copy-of>
 </xsl:function>

 <xsl:function name="gdpr:rngRecurse">
     <xsl:param name="chars"></xsl:param>
     <xsl:param name="rngGenerator"></xsl:param>
     <xsl:param name="newText"></xsl:param>
     <xsl:variable name="curentchar" select="$chars[1]"></xsl:variable>
     <xsl:variable name="newRngGenerator" select="$rngGenerator?next()"/>
     <xsl:choose>
         <xsl:when test="count($chars) >1">
             <xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
             <xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
             <xsl:copy-of select="gdpr:rngRecurse(subsequence($chars,2),$newRngGenerator,$resultText)"></xsl:copy-of>
         </xsl:when>
         <xsl:when test="count($chars) =1">
             <xsl:variable name="transformedChar" select="gdpr:randomLetter2($newRngGenerator,$curentchar)"/>
             <xsl:variable name="resultText" select="concat($newText, $transformedChar)"/>
             <xsl:copy-of select="$resultText"></xsl:copy-of>
         </xsl:when>
         <xsl:otherwise><xsl:copy-of select="$newText"></xsl:copy-of></xsl:otherwise>
     </xsl:choose>

 </xsl:function>
person Erik Zander    schedule 30.04.2019
comment
Кажется, вы сможете использовать шаблон fold-left, предложенный Майклом Каем, например. fold-left(functx:chars($text), random-number-generator(), function($z, $c){ head($z)?next(), tail($z), gdpr:randomLetter2(head($z), $c)}) => tail() => string-join(). - person Martin Honnen; 30.04.2019
comment
Да, это, скорее всего, сработает, я просто справился с тем, что закончил, поэтому у меня не было времени проверить вас. Но ваше решение, безусловно, выглядит круто, и я вам за это благодарен! Придется попробовать, если мне придется вернуться к этому. @MartinHonnen - person Erik Zander; 17.05.2019