101 lines
3.2 KiB
Rust
101 lines
3.2 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
|
|
use crate::FxHasher;
|
|
|
|
/// Type alias for a hashmap using the `fx` hash algorithm with [`FxRandomState`].
|
|
pub type FxHashMapRand<K, V> = HashMap<K, V, FxRandomState>;
|
|
|
|
/// Type alias for a hashmap using the `fx` hash algorithm with [`FxRandomState`].
|
|
pub type FxHashSetRand<V> = HashSet<V, FxRandomState>;
|
|
|
|
/// `FxRandomState` is an alternative state for `HashMap` types.
|
|
///
|
|
/// A particular instance `FxRandomState` will create the same instances of
|
|
/// [`Hasher`], but the hashers created by two different `FxRandomState`
|
|
/// instances are unlikely to produce the same result for the same values.
|
|
#[derive(Clone)]
|
|
pub struct FxRandomState {
|
|
seed: usize,
|
|
}
|
|
|
|
impl FxRandomState {
|
|
/// Constructs a new `FxRandomState` that is initialized with random seed.
|
|
pub fn new() -> FxRandomState {
|
|
use rand::Rng;
|
|
use std::{cell::Cell, thread_local};
|
|
|
|
// This mirrors what `std::collections::hash_map::RandomState` does, as of 2024-01-14.
|
|
//
|
|
// Basically
|
|
// 1. Cache result of the rng in a thread local, so repeatedly
|
|
// creating maps is cheaper
|
|
// 2. Change the cached result on every creation, so maps created
|
|
// on the same thread don't have the same iteration order
|
|
thread_local!(static SEED: Cell<usize> = {
|
|
Cell::new(rand::thread_rng().gen())
|
|
});
|
|
|
|
SEED.with(|seed| {
|
|
let s = seed.get();
|
|
seed.set(s.wrapping_add(1));
|
|
FxRandomState { seed: s }
|
|
})
|
|
}
|
|
}
|
|
|
|
impl core::hash::BuildHasher for FxRandomState {
|
|
type Hasher = FxHasher;
|
|
|
|
fn build_hasher(&self) -> Self::Hasher {
|
|
FxHasher::with_seed(self.seed)
|
|
}
|
|
}
|
|
|
|
impl Default for FxRandomState {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::thread;
|
|
|
|
use crate::FxHashMapRand;
|
|
|
|
#[test]
|
|
fn cloned_random_states_are_equal() {
|
|
let a = FxHashMapRand::<&str, u32>::default();
|
|
let b = a.clone();
|
|
|
|
assert_eq!(a.hasher().seed, b.hasher().seed);
|
|
}
|
|
|
|
#[test]
|
|
fn random_states_are_different() {
|
|
let a = FxHashMapRand::<&str, u32>::default();
|
|
let b = FxHashMapRand::<&str, u32>::default();
|
|
|
|
// That's the whole point of them being random!
|
|
//
|
|
// N.B.: `FxRandomState` uses a thread-local set to a random value and then incremented,
|
|
// which means that this is *guaranteed* to pass :>
|
|
assert_ne!(a.hasher().seed, b.hasher().seed);
|
|
}
|
|
|
|
#[test]
|
|
fn random_states_are_different_cross_thread() {
|
|
// This is similar to the test above, but uses two different threads, so they both get
|
|
// completely random, unrelated values.
|
|
//
|
|
// This means that this test is technically flaky, but the probability of it failing is
|
|
// `1 / 2.pow(bit_size_of::<usize>())`. Or 1/1.7e19 for 64 bit platforms or 1/4294967295
|
|
// for 32 bit platforms. I suppose this is acceptable.
|
|
let a = FxHashMapRand::<&str, u32>::default();
|
|
let b = thread::spawn(|| FxHashMapRand::<&str, u32>::default())
|
|
.join()
|
|
.unwrap();
|
|
|
|
assert_ne!(a.hasher().seed, b.hasher().seed);
|
|
}
|
|
}
|