xquery 随机选择文件而不复制选择

xquery randomly selecting files without duplicating the selection

在 Xquery 3.1 中(在 eXist 4.7 中)我有 40 个 XML 文件,我需要随机 select 其中 4 个。但是我希望这四个文件不同。

我的文件都在同一个集合中 ($data)。我目前对文件进行计数,然后使用随机函数 (util:random($max as xs:integer)) 生成文件序列 position() 到 select 其中四个:

let $filecount := count($data)
for $cnt in 1 to 4
let $pos := util:random($filecount)
return $data[position()=$pos]

但这通常会导致相同的文件偶然被 select编辑多次。

每个文件都有一个不同的 @xml:id(在每个文件的根节点中),如果可能的话,这可以让我在递归中将其用作某种谓词。但我无法找到一种方法,以某种方式将 @xml:id 累积成一个累积的递归序列。

感谢您的帮助。

我认为标准化的 random-numer-generator 函数及其 permute 函数 (https://www.w3.org/TR/xpath-functions/#func-random-number-generator) 应该会给您更好的 "randomness" 和多样化的结果,例如

let $file-count := count($data)
return $data[position() = random-number-generator(current-dateTime())?permute(1 to $file-count)[position() le 4]]

我还没有在您的 db/XQuery 实施中尝试过,您当前使用的功能可能也有一些方法。

对于 eXist-db 我想一个策略是调用 random-number 函数,直到你得到一个不同的所需数量的值序列,下面的 returns(至少在一些使用 eXide 测试)) 每次调用 1 到 40 之间的四个不同数字:

declare function local:random-sequence($max as xs:integer, $length as xs:integer) as xs:integer+ {
    local:random-sequence((), $max, $length)
};

declare function local:random-sequence($seq as xs:integer*, $max as xs:integer, $length as xs:integer) as xs:integer+ {
    if (count($seq) = $length and $seq = distinct-values($seq))
    then $seq
    else local:random-sequence((distinct-values($seq), util:random($max)), $max, $length)
};

let $file-count := 40
return local:random-sequence($file-count, 4)

在之前的尝试中整合它会导致

let $file-count := count($data)
return $data[position() = local:random-sequence($file-count, 4)]

至于你的评论,我没有注意到存在 util:random 函数可以 return 0 并排除最大值所以根据你的评论和进一步的测试我猜你更想要我在上面发布的功能将实现为

declare function local:random-sequence($seq as xs:integer*, $max as xs:integer, $length as xs:integer) as xs:integer+ {
    if (count($seq) = $length)
    then $seq
    else
        let $new-number := util:random($max + 1)
        return if ($seq = $new-number or $new-number = 0)
               then local:random-sequence($seq, $max, $length)
               else local:random-sequence(($seq, $new-number), $max, $length)
};

希望现在 returns $length 1$max 参数之间的不同值。

这是一个非常有趣的问题和有趣的答案,我忍不住要玩 local:random-sequence。这是我想出的:

(: needs zero-check, would return 1 item otherwise :)
declare function local:random-sequence($max as xs:integer, $length as xs:integer) as xs:integer* {
    if ($length = 0)
    then ()
    else local:random-sequence((), $max, $length)
};

declare function local:random-sequence($seq as xs:integer*, $max as xs:integer, $length as xs:integer) as xs:integer+ {
    let $new-number := util:random($max) + 1
    let $new-seq :=
        if ($seq = $new-number)
        then $seq
        else ($seq, $new-number)

    return
        if (count($new-seq) >= $length)
        then $new-seq
        else local:random-sequence($new-seq, $max, $length)
};

我认为它更容易阅读和掌握。它还节省了 1 个函数调用 ;)