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()?_item
The 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 tonext
that 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()?_item
The 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())?sequence
It also works with the built-in random number generator.
dicey:sequence(9, random-number-generator())?sequence
dicey: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())?array
It also works with the built-in random number generator.
dicey:array(9, random-number-generator())?array
Read 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)?sequence
It 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)?_item
For decimals:
dicey:ranged-random(-1.71, 2.46)?_item
Those 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())?sequence
dicey: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")?_item
There 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())
)?sequence
With 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())
)?array
Of 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()))?sequence
A 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)))?array
While 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 i
gulp install
builds the XAR-package and uploads it to the server defined in .existdb.json.
gulp watch
watches 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 test
MIT