rand-sequence
Please note, this crate has been renamed in favour of rand-unique, which better describes the functionality of this crate. If you would like to take ownership of the rand-sequence
crate name, please raise a GitHub issue.
Deterministically generate a sequence of unique random numbers. A non-repeating pseudo-random number generator, directly index-able for the nth number in the sequence.
Not cryptographically secure. Complexity: O(1) time and space complexity for all operations.
Properties:
- The sequence is deterministic and repeatable for the same seeds.
- The sequence will only include each number once (every index has a unique output).
- The sequence is pseudo-uniformly distributed.
- Each number which has not yet appeared in the sequence has a roughly equal probability of being the next number in the sequence.
- Note that once a number has appeared in the sequence, it will not appear again. Each value in this sequence is unique.
- Computing the value for any random index in the sequence is an O(1) operation.
RandomSequence::n(index)
returns the output for a given position in the sequence.
- Support for
u8
,u16
,u32
,u64
, andusize
. Outputs can be cast toi8
,i16
,i32
,i64
, andisize
respectively.
Features
This crate is no-std compatible.
default-features
:rand
rand
: Enables therand(&mut RngCore)
helper methods onRandomSequenceBuilder
andRandomSequence
to initialize with random seeds, which requires therand
dependency. Can be omitted and instead manually provide seeds to theRandomSequenceBuilder::seed()
method to instantiate.serde
: Enables serdeSerlialize
andDeserialize
support forRandomSequenceBuilder
, which requires theserde
dependency.
Example
use OsRng;
use ;
// Initialise a sequence from a random seed.
let config = rand;
let mut sequence = config.into_iter;
// Iterate over the sequence with next() and prev(), or index directly with n(i).
assert_eq!;
assert_eq!;
assert_eq!;
// Unique across the entire type, with support for u8, u16, u32, and u64.
let sequence = rand;
let nums: HashSet =
.into_iter
.map
.collect;
assert_eq!;
// Serialise the config to reproduce the same sequence later. Requires the
// "serde" feature to be enabled.
// let config = serde_json::to_string(&sequence.config).unwrap();
Output Distribution
Future work could include a more rigorous analysis of the output distribution. For now, the following charts demonstrate the roughly uniform distribution for RandomSequence<u16>
.
Histogram visualisation of the RandomSequence
output distribution.
Visual scatter plot of the RandomSequence
output.
How It Works
This non-repeating pseudo-random number generator works by creating a permutation function against the index in the sequence, herein referred to as x
. So for any position x
in the sequence, we want to deterministically compute a unique output number via function n(x)
, where comparing n(x)
and n(x + 1)
would appear randomly generated.
For any prime number $p$ which satisfies $p 3 \mod 4$, then for any input $x$, the operation $f(x) = x^2 \mod p$ will produce a unique number for each value of $x$ where $2x < p$.
Quadratic residue tends to cluster numbers together, and so we apply the quadratic residue permutation along with other permutation functions (wrapping addition and xor) to add further noise. Permutation functions are those with a direct 1-1 mapping for all inputs to outputs, where each input has a unique output.
In a simplified form, the permutation function is:
/// `p` is chosen to be the largest number satisfying:
/// - a prime number
/// - that satisfies p = 3 mod 4 (`p % 4 == 3`)
/// - that fits in the datatype chosen, in this example `u64`
const PRIME: u64 = 18446744073709551427;
/// Simplified example of the quadratic residue function, taking input `x` for prime `PRIME`.
/// Randomly selected variables to introduce further noise in the output generation.
const OFFSET_NOISE: u64 = 0x46790905682f0161;
const XOR_NOISE: u64 = 0x5bf0363546790905;
/// We can then use this permutation function [permute_qpr] to build our number generator `n(x)`.
Sources
Based on the article by @preshing using quadratic prime residue: