Skip to content

Commit

Permalink
Merge pull request #4 from hoxxep/exact-size-iterator
Browse files Browse the repository at this point in the history
Add usize impl and ExactSizeIterator for u8, u16, and u32
  • Loading branch information
hoxxep authored Nov 2, 2023
2 parents c9f2847 + cd42e9d commit 5ae7c1a
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rand-sequence"
version = "0.1.1"
version = "0.2.0"
edition = "2021"
authors = ["Liam Gray <gmail@liamg.me>"]
description = "A no-std crate for generating random sequences of unique integers in O(1) time."
Expand Down
33 changes: 17 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,15 @@ Properties:
- 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`, and `u64`. Outputs can be cast to `i8`, `i16`, `i32`, and `i64` respectively.

## 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.
![Histogram demonstrating uniformity of distribution](https://github.com/hoxxep/rand-sequence/raw/master/charts/histogram-u16.png)

Visual scatter plot of the `RandomSequence` output.
![Scatter plot of RandomSequence output](https://github.com/hoxxep/rand-sequence/raw/master/charts/scatter-u16.png)
- Support for `u8`, `u16`, `u32`, `u64`, and `usize`. Outputs can be cast to `i8`, `i16`, `i32`, `i64`, and `isize` respectively.

## Features

This crate is no-std compatible.

- `default-features`: `rand`
- `rand`: Enables the `rand(&mut RngCore)` helper methods on `RandomSequenceBuilder` and `RandomSequence` to initialize with random seeds, which requires the `rand` dependency. Can be omitted and instead manually provide seeds to the `RandomSequenceBuilder::seed()` method to instantiate.
- `serde`: Enables serde support for `RandomSequenceBuilder`, which requires the `serde` dependency.
- `serde`: Enables serde `Serlialize` and `Deserialize` support for `RandomSequenceBuilder`, which requires the `serde` dependency.

## Example

Expand All @@ -43,9 +33,9 @@ let config = RandomSequenceBuilder::<u64>::rand(&mut OsRng);
let mut sequence = config.into_iter();

// Iterate over the sequence with next() and prev(), or index directly with n(i).
assert_eq!(sequence.next(), sequence.n(0));
assert_eq!(sequence.next(), sequence.n(1));
assert_eq!(sequence.next(), sequence.n(2));
assert_eq!(sequence.next().unwrap(), sequence.n(0));
assert_eq!(sequence.next().unwrap(), sequence.n(1));
assert_eq!(sequence.next().unwrap(), sequence.n(2));

// Unique across the entire type, with support for u8, u16, u32, and u64.
let sequence = RandomSequence::<u16>::rand(&mut OsRng);
Expand All @@ -55,10 +45,21 @@ let nums: std::collections::HashSet<u16> = (0..=u16::MAX)
.collect();
assert_eq!(nums.len(), u16::MAX as usize + 1);

// Serialise the config to reproduce is later, if the "serde" feature is enabled.
// 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.
![Histogram demonstrating uniformity of distribution](https://github.com/hoxxep/rand-sequence/raw/master/charts/histogram-u16.png)

Visual scatter plot of the `RandomSequence` output.
![Scatter plot of RandomSequence output](https://github.com/hoxxep/rand-sequence/raw/master/charts/scatter-u16.png)

## 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.
Expand Down
2 changes: 2 additions & 0 deletions benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod sequence {
group.bench_function("n_u16", bench_n_u16);
group.bench_function("n_u32", bench_n_u32);
group.bench_function("n_u64", bench_n_u64);
group.bench_function("n_usize", bench_n_usize);
group.bench_function("rand_u64", bench_rand_u64);
}

Expand All @@ -35,6 +36,7 @@ mod sequence {
bench_n!(bench_n_u16, u16);
bench_n!(bench_n_u32, u32);
bench_n!(bench_n_u64, u64);
bench_n!(bench_n_usize, usize);

/// Compare standard random number generation time.
fn bench_rand_u64(b: &mut Bencher) {
Expand Down
48 changes: 40 additions & 8 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use num_traits::{PrimInt, WrappingAdd, WrappingSub};
use num_traits::{AsPrimitive, PrimInt, WrappingAdd, WrappingSub};

use crate::sequence::RandomSequence;

Expand All @@ -21,9 +21,12 @@ use crate::sequence::RandomSequence;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RandomSequenceBuilder<T>
where
T: PrimInt + WrappingAdd + WrappingSub + QuadraticResidue
T: QuadraticResidue
{
/// The seed for the the start index.
pub seed_base: T,

/// The seed for the offsetting addition.
pub seed_offset: T,

/// A value used as an xor during initialisation for `start_index = f(seed_base, init_base)` to
Expand All @@ -43,7 +46,7 @@ where

impl<T> RandomSequenceBuilder<T>
where
T: PrimInt + WrappingAdd + WrappingSub + QuadraticResidue
T: QuadraticResidue
{
/// Initialise a config from stored settings. Not recommended unless you know what you're doing,
/// or these values have been taken from an already serialized RandomSequenceBuilder.
Expand Down Expand Up @@ -92,7 +95,8 @@ where

impl<T> IntoIterator for RandomSequenceBuilder<T>
where
T: PrimInt + WrappingAdd + WrappingSub + QuadraticResidue
T: QuadraticResidue,
RandomSequence<T>: Iterator<Item = T>,
{
type Item = T;
type IntoIter = RandomSequence<T>;
Expand All @@ -105,13 +109,15 @@ where
RandomSequence {
config: self,
start_index,
current_index: start_index,
current_index: T::zero(),
intermediate_offset,
ended: false,
}
}
}

impl RandomSequenceBuilder<u8> {
/// Initialise a [RandomSequenceBuilder] from a specific seed pair.
pub fn new(seed_base: u8, seed_offset: u8) -> Self {
Self {
seed_base,
Expand All @@ -125,6 +131,7 @@ impl RandomSequenceBuilder<u8> {
}

impl RandomSequenceBuilder<u16> {
/// Initialise a [RandomSequenceBuilder] from a specific seed pair.
pub fn new(seed_base: u16, seed_offset: u16) -> Self {
Self {
seed_base,
Expand All @@ -138,6 +145,7 @@ impl RandomSequenceBuilder<u16> {
}

impl RandomSequenceBuilder<u32> {
/// Initialise a [RandomSequenceBuilder] from a specific seed pair.
pub fn new(seed_base: u32, seed_offset: u32) -> Self {
Self {
seed_base,
Expand All @@ -151,26 +159,44 @@ impl RandomSequenceBuilder<u32> {
}

impl RandomSequenceBuilder<u64> {
/// Initialise a [RandomSequenceBuilder] from a specific seed pair.
pub fn new(seed_base: u64, seed_offset: u64) -> Self {
Self {
seed_base,
seed_offset,
init_base: 0x682f01615bf03635,
init_offset: 0x46790905682f0161,
prime: 18446744073709551427, // largest prime: 18446744073709551557
prime: 18446744073709551427,
intermediate_xor: 0x5bf0363546790905,
}
}
}

pub trait QuadraticResidue {
impl RandomSequenceBuilder<usize> {
/// Initialise a [RandomSequenceBuilder] from a specific seed pair.
pub fn new(seed_base: usize, seed_offset: usize) -> Self {
Self {
seed_base,
seed_offset,
init_base: 0x682f01615bf03635,
init_offset: 0x46790905682f0161,
prime: 18446744073709551427,
intermediate_xor: 0x5bf0363546790905,
}
}
}

pub trait QuadraticResidue
where
Self: PrimInt + AsPrimitive<usize> + WrappingAdd + WrappingSub
{
/// Compute the quadratic residue of this number against a prime.
fn residue(self, prime: Self) -> Self;
}

macro_rules! impl_residue {
($base_type:ident, $larger_type:ident) => {
impl QuadraticResidue for $base_type {
/// Compute the quadratic residue of this number against a prime.
fn residue(self, prime: Self) -> Self {
((self as $larger_type * self as $larger_type) % prime as $larger_type) as Self
}
Expand All @@ -182,6 +208,12 @@ impl_residue!(u8, u16);
impl_residue!(u16, u32);
impl_residue!(u32, u64);
impl_residue!(u64, u128);
#[cfg(target_pointer_width = "64")]
impl_residue!(usize, u128);
#[cfg(target_pointer_width = "32")]
impl_residue!(usize, u64);
#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
compile_error!("Unsupported pointer width.");

#[cfg(test)]
mod tests {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
#![no_std]
#[cfg(test)]
extern crate std;
Expand Down
1 change: 1 addition & 0 deletions src/rand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ init_rand!(u8, tests_u8);
init_rand!(u16, tests_u16);
init_rand!(u32, tests_u32);
init_rand!(u64, tests_u64);
init_rand!(usize, tests_usize);
1 change: 1 addition & 0 deletions src/seed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ seed_sequence!(u8);
seed_sequence!(u16);
seed_sequence!(u32);
seed_sequence!(u64);
seed_sequence!(usize);
Loading

0 comments on commit 5ae7c1a

Please sign in to comment.