xquery случайный выбор файлов без дублирования выбора - PullRequest
3 голосов
/ 23 октября 2019

В Xquery 3.1 (в eXist 4.7) у меня есть 40 файлов XML, и мне нужно выбрать 4 из них случайным образом. Однако мне бы хотелось, чтобы четыре файла были разными.

Все мои файлы находятся в одной коллекции ($data). В настоящее время я считаю файлы, затем использую функцию рандомизации ( util: random ($ max как 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 в кумулятивную рекурсивную последовательность.

Спасибо за любую помощь.

Ответы [ 2 ]

4 голосов
/ 23 октября 2019

Я думаю, что стандартизированная функция 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.

2 голосов
/ 25 октября 2019

Это был такой забавный вопрос и интересный ответ, что я ничего не мог с собой поделать, как играть с 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 вызов функции;)

...