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

时间:2019-10-22 22:17:47

标签: xpath xquery exist-db

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

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

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

但这经常导致同一文件被偶然选择多次。

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

感谢您的帮助。

2 个答案:

答案 0 :(得分:4)

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

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函数,直到获得所需数量的值的不同序列,以下返回(至少在某些使用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函数可以返回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)
};

这样,它希望现在返回$length1参数之间的$max个不同的值。

答案 1 :(得分:2)

这是一个很有趣的问题和很有趣的答案,我除了与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个函数调用;)