Just a bunch of random functions
Import the module with
import module namespace dicey="http://line-o.de/xq/dicey";Now you can throw a (six-sided) dice.
dicey:d6()?_itemThe library augments the default fn:random-number-generator in several ways.
So, you can use dicey random number generators as if they were the XQuery built-ins.
The added functionality lies in additional keys in the returned map
_dicey: if this is true() you have an augmented random at your hands_item: a thing derived from the current random number_next: a wrapped call tonextthat will produce the next augmented generator of the current type
For dicey:d6, for example, _item will always be a xs:integer between 1 and 6.
dicey:d6()?_next()?_next()?_itemThe underscores might be an acquired taste, but the decision was made after reading the specification of fn:random-number-generator. Specifically this sentence struck a chord:
The map returned by the fn:random-number-generator function may contain additional entries beyond those specified here, but it must match the type map(xs:string, item()). The meaning of any additional entries is ·implementation-defined·. To avoid conflict with any future version of this specification, the keys of any such entries should start with an underscore character.
Is latin for "the dice have fallen". There are two functions that are useful whenever you need more than one random value.
dicey:sequence and dicey:array. The main difference between the two
is that one returns a sequence and the other an array (the name gives it away).
Throw one dice three times in a row:
dicey:sequence(3, dicey:d6())?sequenceIt also works with the built-in random number generator.
dicey:sequence(9, random-number-generator())?sequencedicey:sequence returns a map with:
- sequence: the sequence of n random items
- generator: the random number generator in use
The sequence key value is what you are usually after.
dicey:array is almost the same, but returns a map with:
- array: the array of n random items
- generator: the random number generator in use
dicey:array(3, dicey:d6())?arrayIt also works with the built-in random number generator.
dicey:array(9, random-number-generator())?arrayRead on to learn what the generator key is about.
When you provide your seeded random, throwing a dice will have a reproducible outcome.
let $piked-dice := dicey:d6(random-number-generator(103))
return dicey:sequence(6, $piked-dice)?sequenceIt is also interesting to continue using the same dice across different uses.
let $piked-dice := dicey:d6(random-number-generator(103))
let $first-batch := dicey:sequence(6, $piked-dice)
return (
$first-batch?sequence,
dicey:sequence(6, $first-batch?_next())
)Or get a hold of the plain random-number-generator again. That way you can set up a different dice and throw that, for example.
let $piked-dice := dicey:d6(random-number-generator(103))
let $d20 := dicey:d20($piked-dice?next())
return (
$piked-dice?_item,
$d20?_item
)What if you need a random number in an arbitrary range?
For integers:
dicey:ranged-random-integer(1, 1000000)?_itemFor decimals:
dicey:ranged-random(-1.71, 2.46)?_itemThose two can of course be used with dicey:sequence.
They also both have a signature that accepts a random number generator as the third parameter.
If you want to draw n items from a sequence, so that each item will only
be returned once, you can use the permute function returned by random-number-generator.
random-number-generator()?permute($sequence)
=> subsequence(1, $n)Or, you can use dicey:draw to achieve the same result, but lazily by (re)moving items
at random indeces. The function can draw from both, arrays and sequences. The result is
returned in the corresponding key.
dicey:draw($n, $from, random-number-generator())?sequencedicey:draw returns a map with following properties:
- sequence: the sequence of n items that were drawn, if a sequence was provided as $from
- array: the array of n items that were drawn, if an array was provided as $from
- from: the remainder of items from the original sequence
- generator: the random number generator in use
You can access the two implementations dicey:draw-from-sequence and dicey:draw-from-array
directly. That way you can be certain which key the result is in.
Drawing a few items from a large stash (> 10K items) is much faster than permuting it.
The library can help you pick all kinds of data at random. That is particularly useful for assembling test-data.
To construct a random string from a set of characters there is a special function
dicey:random-from-characers.
A "word" with ten random small latin characters can be generated with:
dicey:random-from-characters(10, "abcdefghijklmnopqrstuvwxyz")?_itemThere is also a signature to provide your generator
dicey:random-from-characters(10, "abcdefghijklmnopqrstuvwxyz", random-number-generator(103))Pass a list of items to dicey:random-from and it will pick one of them at random.
dicey:random-from returns a map with following properties:
- sequence: the sequence of n items that were picked, if a sequence was provided as $from
- array: the array of n items that were picked, if an array was provided as $from
- from: the remainder of items from the original sequence
- generator: the random number generator in use
As with dicey:draw earlier dicey:pick can also handle arrays and sequences.
let $stuff-to-pick-from :=
(
map {
"name": "alice",
"id": 1
},
map {
"name": "bob",
"id": 2
}
)
dicey:sequence(1,
dicey:pick(
$stuff-to-pick-from, random-number-generator())
)?sequenceWith arrays you can also have empty sequences in your options, which can be very handy.
let $might-be-empty :=
[
map {
"name": "alice",
"id": 1
},
()
]
dicey:array(1,
dicey:picks(
$might-be-empty, random-number-generator())
)?arrayOf course you can deliberately use dicey:pick-from-array and dicey:pick-from-sequence.
You can use the functions dicey provides to build functions that generate random values in other
domains.
The example from the previous section can be generalized into a function creating fake users.
xquery version "3.1";
import module namespace dicey="http://line-o.de/xq/dicey";
declare variable $local:names := ("alice", "bob");
declare function local:random-user ($generator as map(xs:string, item())) as map(xs:string, item()) {
let $fake-user := map {
"id": dicey:ranged-random-integer(1, 10000, $generator)?_item,
"name": dicey:random-from($local:names, $generator)?_item
}
return
map:merge((
map {
"_dicey": true(),
"_item": $fake-user,
"_next": function() {
local:random-user($generator?next())
}
},
$generator
))
};
dicey:sequence(2,
local:random-user(
random-number-generator()))?sequenceA generator creating random colors in CSS' functional rgb notation:
xquery version "3.1";
import module namespace dicey="http://line-o.de/xq/dicey";
declare function local:rgb ($generator as map(xs:string, item())) as map(xs:string, item())s {
let $result := dicey:sequence(3, dicey:ranged-random-integer(0, 255, $generator))
let $color := ``[rgb(`{string-join($result?sequence, ",")}`)]``
return map:merge((
$result?generator,
map {
"_dicey": true(),
"_item": $color,
"_next": function () { local:rgb($result?generator?next()) }
}
))
};
(: example output:
[
"rgb(183,185,220)",
"rgb(200,187,39)",
"rgb(14,43,23)"
]
:)
dicey:array(3,
local:rgb(random-number-generator(103)))?arrayWhile the primary target is eXistdb (starting from version 5.3.0) the library module itself should be compatible with any XQuery 3.1 runtime (e.g. saxon 10, baseX 9.5.x).
I am keen to hear your feedback and welcome additional tests, examples and documentation. If you find a bug or want to propose a new feature please open an issue or pull request.
To make developing as seamless as possible some npm and gulp scripts are included in the
project.
- node 12+
Install dependencies with
npm igulp installbuilds the XAR-package and uploads it to the server defined in .existdb.json.
gulp watchwatches for changes in either the library module, the specs or the testrunner and will package the XAR and upload it to the database instance when changes are saved to disk.
The XQSuite with tests can be run from
within existdb using the testrunner or
from the commandline using npm.
npm testMIT