xquery 随机选择文件而不重复选择
Posted
技术标签:
【中文标题】xquery 随机选择文件而不重复选择【英文标题】:xquery randomly selecting files without duplicating the selection 【发布时间】:2020-02-19 02:50:36 【问题描述】:在 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
s 累积到累积的递归序列中的方法。
感谢您的帮助。
【问题讨论】:
【参考方案1】:我认为标准化的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)
;
这样,它现在有望返回 $length
和 1
和 $max
参数之间的不同值。
【讨论】:
我已经尝试了在线 exide 网页,并且可以让let $file-count := 40 return random-number-generator((current-dateTime() - xs:dateTime("1970-01-01T00:00:00-00:00")) div xs:dayTimeDuration('PT1S')) ?permute(1 to $file-count)[position() le 4]
在其中工作以返回 1 到 40 之间的四个随机数序列。
确实如此。原来是only available as of eXist 5.0(上个月发布,是你使用的公共eXide的环境)。不幸的是,这使我实际上无法使用该功能。
@jbrehr,请参阅编辑,除非 eXist 专家可以通过某种方式告诉您如何确保其随机生成器提供更好的随机性,您可以尝试调用该函数,直到 distinct-values 向您保证结果是四个(或您需要的任何数字)不同的值。
@jbrehr,我已更改为在答案中执行该函数以排除任何 0
值,并调整该函数以将 util:random
函数与 $max + 1
一起使用,以便最大值(例如40
)也可以返回。
@line-o,非常好的一点,是的,我最初尝试简单地依赖 XPath 3.1 方式来最终获得与原始发布者版本的 eXist I 兼容的东西所做的所有更改忽略了使用 th 存在函数的简单变化。【参考方案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 个函数调用;)
【讨论】:
以上是关于xquery 随机选择文件而不重复选择的主要内容,如果未能解决你的问题,请参考以下文章