#![allow(rustdoc::bare_urls)]
#![doc = include_str!("../README.md")]
use std::hash::{BuildHasher, Hash, Hasher};
mod hasher;
pub use hasher::DefaultHasher;
mod builder;
pub use builder::{BuilderWithBits, BuilderWithFalsePositiveRate};
mod bit_vector;
use bit_vector::BlockedBitVec;
mod sparse_hash;
use sparse_hash::SparseHash;
use wide::{u64x2, u64x4};
#[derive(Debug, Clone)]
pub struct BloomFilter<const BLOCK_SIZE_BITS: usize = 512, S = DefaultHasher> {
bits: BlockedBitVec<BLOCK_SIZE_BITS>,
target_hashes: u64,
num_rounds: Option<u64>,
num_hashes: u64,
hasher: S,
}
impl BloomFilter {
fn new_builder<const BLOCK_SIZE_BITS: usize>(
num_bits: usize,
) -> BuilderWithBits<BLOCK_SIZE_BITS> {
assert!(num_bits > 0);
let num_u64s = num_bits.div_ceil(64);
BuilderWithBits::<BLOCK_SIZE_BITS> {
data: vec![0; num_u64s],
hasher: Default::default(),
}
}
fn new_from_vec<const BLOCK_SIZE_BITS: usize>(
vec: Vec<u64>,
) -> BuilderWithBits<BLOCK_SIZE_BITS> {
assert!(!vec.is_empty());
BuilderWithBits::<BLOCK_SIZE_BITS> {
data: vec,
hasher: Default::default(),
}
}
fn new_with_false_pos<const BLOCK_SIZE_BITS: usize>(
fp: f64,
) -> BuilderWithFalsePositiveRate<BLOCK_SIZE_BITS> {
assert!(fp > 0.0);
BuilderWithFalsePositiveRate::<BLOCK_SIZE_BITS> {
desired_fp_rate: fp,
hasher: Default::default(),
}
}
pub fn with_false_pos(fp: f64) -> BuilderWithFalsePositiveRate<512> {
BloomFilter::new_with_false_pos::<512>(fp)
}
pub fn with_num_bits(num_bits: usize) -> BuilderWithBits<512> {
BloomFilter::new_builder::<512>(num_bits)
}
pub fn from_vec(bit_vec: Vec<u64>) -> BuilderWithBits<512> {
BloomFilter::new_from_vec::<512>(bit_vec)
}
}
impl<const BLOCK_SIZE_BITS: usize, S: BuildHasher> BloomFilter<BLOCK_SIZE_BITS, S> {
const BIT_INDEX_MASK: u64 = (BLOCK_SIZE_BITS - 1) as u64;
#[inline]
fn optimal_hashes_f(items_per_block: f64) -> f64 {
let block_size = BLOCK_SIZE_BITS as f64;
let min_hashes_mult = (BLOCK_SIZE_BITS as f64) / (512f64);
let max_hashes = block_size / 64.0f64 * sparse_hash::hashes_for_bits(32) * min_hashes_mult;
let hashes_per_block = block_size / items_per_block * f64::ln(2.0f64);
if hashes_per_block > max_hashes {
max_hashes
} else {
if hashes_per_block < 1.0 {
1.0
} else {
hashes_per_block
}
}
}
#[inline]
fn bit_index(hash1: &mut u64, hash2: u64) -> usize {
let h = u64::next_hash(hash1, hash2);
(h & Self::BIT_INDEX_MASK) as usize
}
#[inline]
pub fn insert(&mut self, val: &(impl Hash + ?Sized)) -> bool {
let [mut h1, h2] = get_orginal_hashes(&self.hasher, val);
let mut previously_contained = true;
for _ in 0..self.num_hashes {
let index = block_index(self.num_blocks(), h1);
let block = &mut self.bits.get_block_mut(index);
previously_contained &= BlockedBitVec::<BLOCK_SIZE_BITS>::set_for_block(
block,
Self::bit_index(&mut h1, h2),
);
}
if let Some(num_rounds) = self.num_rounds {
let index = block_index(self.num_blocks(), h1);
match BLOCK_SIZE_BITS {
128 => {
let mut hashes_1 = u64x2::h1(&mut h1, h2);
let hashes_2 = u64x2::h2(h2);
let data = u64x2::sparse_hash(&mut hashes_1, hashes_2, num_rounds);
previously_contained &= u64x2::matches(self.bits.get_block(index), data);
u64x2::set(self.bits.get_block_mut(index), data);
}
256 => {
let mut hashes_1 = u64x4::h1(&mut h1, h2);
let hashes_2 = u64x4::h2(h2);
let data = u64x4::sparse_hash(&mut hashes_1, hashes_2, num_rounds);
previously_contained &= u64x4::matches(self.bits.get_block(index), data);
u64x4::set(self.bits.get_block_mut(index), data);
}
512 => {
let hashes_2 = u64x4::h2(h2);
let mut hashes_1 = u64x4::h1(&mut h1, h2);
for i in 0..2 {
let data = u64x4::sparse_hash(&mut hashes_1, hashes_2, num_rounds);
previously_contained &=
u64x4::matches(&self.bits.get_block(index)[4 * i..], data);
u64x4::set(&mut self.bits.get_block_mut(index)[4 * i..], data);
}
}
_ => {
for i in 0..self.bits.get_block(index).len() {
let data = u64::sparse_hash(&mut h1, h2, num_rounds);
let block = &mut self.bits.get_block_mut(index);
previously_contained &= (block[i] & data) == data;
block[i] |= data;
}
}
}
}
previously_contained
}
#[inline]
pub fn contains(&self, val: &(impl Hash + ?Sized)) -> bool {
let [mut h1, h2] = get_orginal_hashes(&self.hasher, val);
(0..self.num_hashes).into_iter().all(|_| {
let index = block_index(self.num_blocks(), h1);
let block = &self.bits.get_block(index);
BlockedBitVec::<BLOCK_SIZE_BITS>::check_for_block(block, Self::bit_index(&mut h1, h2))
}) && (if let Some(num_rounds) = self.num_rounds {
let index = block_index(self.num_blocks(), h1);
let block = &self.bits.get_block(index);
match BLOCK_SIZE_BITS {
128 => {
let mut hashes_1 = u64x2::h1(&mut h1, h2);
let hashes_2 = u64x2::h2(h2);
let data = u64x2::sparse_hash(&mut hashes_1, hashes_2, num_rounds);
u64x2::matches(block, data)
}
256 => {
let mut hashes_1 = u64x4::h1(&mut h1, h2);
let hashes_2 = u64x4::h2(h2);
let data = u64x4::sparse_hash(&mut hashes_1, hashes_2, num_rounds);
u64x4::matches(block, data)
}
512 => {
let mut hashes_1 = u64x4::h1(&mut h1, h2);
let hashes_2 = u64x4::h2(h2);
(0..2).all(|i| {
let data = u64x4::sparse_hash(&mut hashes_1, hashes_2, num_rounds);
u64x4::matches(&block[4 * i..], data)
})
}
_ => (0..block.len()).all(|i| {
let data = u64::sparse_hash(&mut h1, h2, num_rounds);
(block[i] & data) == data
}),
}
} else {
true
})
}
#[inline]
pub fn num_hashes(&self) -> u32 {
self.target_hashes as u32
}
pub fn num_bits(&self) -> usize {
self.num_blocks() * BLOCK_SIZE_BITS
}
pub fn num_blocks(&self) -> usize {
self.bits.num_blocks()
}
#[inline]
pub fn as_slice(&self) -> &[u64] {
self.bits.as_slice()
}
#[inline]
pub fn clear(&mut self) {
self.bits.clear();
}
}
impl<T, const BLOCK_SIZE_BITS: usize, S: BuildHasher> Extend<T> for BloomFilter<BLOCK_SIZE_BITS, S>
where
T: Hash,
{
#[inline]
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for val in iter {
self.insert(&val);
}
}
}
impl<const BLOCK_SIZE_BITS: usize, S: BuildHasher> PartialEq for BloomFilter<BLOCK_SIZE_BITS, S> {
fn eq(&self, other: &Self) -> bool {
self.bits == other.bits
&& self.num_hashes == other.num_hashes
&& self.num_rounds == other.num_rounds
}
}
impl<const BLOCK_SIZE_BITS: usize, S: BuildHasher> Eq for BloomFilter<BLOCK_SIZE_BITS, S> {}
#[inline]
pub(crate) fn get_orginal_hashes(
hasher: &impl BuildHasher,
val: &(impl Hash + ?Sized),
) -> [u64; 2] {
let mut state = hasher.build_hasher();
val.hash(&mut state);
let h1 = state.finish();
let h2 = h1.wrapping_shr(32).wrapping_mul(0x51_7c_c1_b7_27_22_0a_95); [h1, h2]
}
#[inline]
pub(crate) fn block_index(num_blocks: usize, hash: u64) -> usize {
(((hash >> 32) as usize * num_blocks) >> 32) as usize
}
#[cfg(test)]
mod tests {
use super::*;
use rand::{rngs::StdRng, Rng, SeedableRng};
use std::{collections::HashSet, iter::repeat};
trait Seeded: BuildHasher {
fn seeded(seed: &[u8; 16]) -> Self;
}
impl Seeded for DefaultHasher {
fn seeded(seed: &[u8; 16]) -> Self {
Self::seeded(seed)
}
}
impl Seeded for ahash::RandomState {
fn seeded(seed: &[u8; 16]) -> Self {
ahash::RandomState::with_seed(seed[0] as usize)
}
}
fn random_strings(num: usize, min_repeat: u32, max_repeat: u32, seed: u64) -> Vec<String> {
let mut rng = StdRng::seed_from_u64(seed);
let gen = rand_regex::Regex::compile(r"[a-zA-Z]+", max_repeat).unwrap();
(&mut rng)
.sample_iter(&gen)
.filter(|s: &String| s.len() >= min_repeat as usize)
.take(num)
.collect()
}
fn random_numbers(num: usize, seed: u64) -> Vec<u64> {
let mut rng = StdRng::seed_from_u64(seed);
repeat(()).take(num).map(|_| rng.gen()).collect()
}
fn block_counts<const N: usize>(filter: &BloomFilter<N>) -> Vec<u64> {
(0..filter.num_blocks())
.map(|i| {
filter
.bits
.get_block(i)
.iter()
.map(|x| x.count_ones() as u64)
.sum()
})
.collect()
}
#[test]
fn test_to_from_vec() {
fn to_from_<const N: usize>(size: usize) {
let vals = random_numbers(100, size as u64);
let mut b = BloomFilter::new_builder::<N>(size).seed(&1).hashes(3);
b.extend(vals.clone());
let x = b.as_slice();
let b2 = BloomFilter::new_from_vec::<N>(x.to_vec())
.seed(&1)
.hashes(3);
assert_eq!(b, b2);
assert_eq!(b.num_blocks() * N, b.as_slice().len() * 64);
assert!(size <= b.as_slice().len() * 64);
assert!((size + N) > b.as_slice().len() * 64);
}
for size in 1..=10009 {
to_from_::<64>(size);
to_from_::<128>(size);
to_from_::<256>(size);
to_from_::<512>(size);
}
}
#[test]
fn first_insert_false() {
let mut filter = BloomFilter::with_num_bits(1202).expected_items(4);
assert!(!filter.insert(&5));
}
#[test]
fn target_fp_is_accurate() {
fn target_fp_is_accurate_<const N: usize>(thresh: f64) {
for mag in 1..=5 {
let fp = 1.0f64 / 10u64.pow(mag) as f64;
for num_items_mag in 1..6 {
let num_items = 10usize.pow(num_items_mag);
let sample_vals = random_numbers(num_items, 42);
let filter = BloomFilter::new_with_false_pos::<N>(fp)
.seed(&42)
.items(sample_vals.iter());
let control: HashSet<u64> = sample_vals.clone().into_iter().collect();
let anti_vals = random_numbers(100_000, 3);
let sample_fp = false_pos_rate_with_vals(&filter, &control, &anti_vals);
if sample_fp > 0.0 {
let score = sample_fp / fp;
assert!(score <= thresh, "score {score:}, block_size: {N:}, size: {num_items:}, fp: {fp:}, sample fp: {sample_fp:}");
}
}
}
}
target_fp_is_accurate_::<512>(5.0);
target_fp_is_accurate_::<256>(5.0);
target_fp_is_accurate_::<128>(10.0);
target_fp_is_accurate_::<64>(75.0);
}
#[test]
fn nothing_after_clear() {
fn nothing_after_clear_<const N: usize>() {
for mag in 1..6 {
let size = 10usize.pow(mag);
for bloom_size_mag in 6..10 {
let num_blocks_bytes = 1 << bloom_size_mag;
let sample_vals = random_numbers(size, 42);
let num_bits = num_blocks_bytes * 8;
let mut filter = BloomFilter::new_builder::<N>(num_bits)
.seed(&7)
.items(sample_vals.iter());
assert!(filter.num_hashes() > 0);
filter.clear();
assert!(sample_vals.iter().all(|x| !filter.contains(x)));
assert_eq!(block_counts(&filter).iter().sum::<u64>(), 0);
}
}
}
nothing_after_clear_::<512>();
nothing_after_clear_::<256>();
nothing_after_clear_::<128>();
nothing_after_clear_::<64>();
}
#[test]
fn random_inserts_always_contained() {
fn random_inserts_always_contained_<const N: usize>() {
for mag in 1..6 {
let size = 10usize.pow(mag);
for bloom_size_mag in 6..10 {
let num_blocks_bytes = 1 << bloom_size_mag;
let sample_vals = random_numbers(size, 42);
let num_bits = num_blocks_bytes * 8;
let mut filter =
BloomFilter::new_builder::<N>(num_bits).items(sample_vals.iter());
assert!(sample_vals.iter().all(|x| filter.contains(x)));
assert!(sample_vals.iter().all(|x| filter.insert(x)));
}
}
}
random_inserts_always_contained_::<512>();
random_inserts_always_contained_::<256>();
random_inserts_always_contained_::<128>();
random_inserts_always_contained_::<64>();
}
#[test]
fn test_optimal_hashes_is_optimal() {
fn test_optimal_hashes_is_optimal_<const BLOCK_SIZE_BITS: usize, H: Seeded>() {
let sizes = [1000, 2000, 5000, 6000, 8000, 10000];
let mut wins = 0;
for num_items in sizes.clone() {
let sample_vals = random_numbers(num_items, 42);
let num_bits = 65000 * 8;
let filter = BloomFilter::new_builder::<BLOCK_SIZE_BITS>(num_bits)
.hasher(H::seeded(&[42; 16]))
.items(sample_vals.clone().into_iter());
let control: HashSet<u64> = sample_vals.clone().into_iter().collect();
let anti_vals = random_numbers(100_000, 3);
let fp_to_beat = false_pos_rate_with_vals(&filter, &control, &anti_vals);
let optimal_hashes = filter.num_hashes();
for num_hashes in [optimal_hashes - 1, optimal_hashes + 1] {
let mut test_filter = BloomFilter::new_builder::<BLOCK_SIZE_BITS>(num_bits)
.hasher(H::seeded(&[42; 16]))
.hashes(num_hashes);
test_filter.extend(sample_vals.clone().into_iter());
let fp = false_pos_rate_with_vals(&test_filter, &control, &anti_vals);
wins += (fp_to_beat <= fp) as usize;
}
}
assert!(wins > sizes.len() / 2);
}
test_optimal_hashes_is_optimal_::<512, DefaultHasher>();
test_optimal_hashes_is_optimal_::<256, DefaultHasher>();
test_optimal_hashes_is_optimal_::<128, DefaultHasher>();
test_optimal_hashes_is_optimal_::<64, DefaultHasher>();
}
#[test]
fn seeded_is_same() {
let num_bits = 1 << 13;
let sample_vals = random_strings(1000, 16, 32, 53226);
for x in 0u8..10 {
let seed = x as u128;
assert_eq!(
BloomFilter::with_num_bits(num_bits)
.seed(&seed)
.items(sample_vals.iter()),
BloomFilter::with_num_bits(num_bits)
.seed(&seed)
.items(sample_vals.iter())
);
assert!(
!(BloomFilter::with_num_bits(num_bits)
.seed(&(seed + 1))
.items(sample_vals.iter())
== BloomFilter::with_num_bits(num_bits)
.seed(&seed)
.items(sample_vals.iter()))
);
}
}
fn false_pos_rate_with_vals<
'a,
const N: usize,
H: BuildHasher,
X: Hash + Eq + PartialEq + 'a,
>(
filter: &BloomFilter<N, H>,
control: &HashSet<X>,
anti_vals: impl IntoIterator<Item = &'a X>,
) -> f64 {
let mut total = 0;
let mut false_positives = 0;
for x in anti_vals.into_iter() {
if !control.contains(x) {
total += 1;
false_positives += filter.contains(x) as usize;
}
}
(false_positives as f64) / (total as f64)
}
#[test]
fn false_pos_decrease_with_size() {
fn false_pos_decrease_with_size_<const N: usize>() {
let anti_vals = random_numbers(1000, 2);
for mag in 5..6 {
let size = 10usize.pow(mag);
let mut prev_fp = 1.0;
let mut prev_prev_fp = 1.0;
for num_bits_mag in 9..22 {
let num_bits = 1 << num_bits_mag;
let sample_vals = random_numbers(size, 1);
let filter = BloomFilter::new_builder::<N>(num_bits).items(sample_vals.iter());
let control: HashSet<u64> = sample_vals.into_iter().collect();
let fp = false_pos_rate_with_vals(&filter, &control, &anti_vals);
let err = format!(
"size: {size:}, num_bits: {num_bits:}, {:.6}, {:?}",
fp,
filter.num_hashes(),
);
assert!(
fp <= prev_fp || prev_fp <= prev_prev_fp || fp < 0.01,
"{}",
err
); prev_prev_fp = prev_fp;
prev_fp = fp;
}
}
}
false_pos_decrease_with_size_::<512>();
false_pos_decrease_with_size_::<256>();
false_pos_decrease_with_size_::<128>();
false_pos_decrease_with_size_::<64>();
}
fn assert_even_distribution(distr: &[u64], err: f64) {
assert!(err > 0.0 && err < 1.0);
let expected: i64 = (distr.iter().sum::<u64>() / (distr.len() as u64)) as i64;
let thresh = (expected as f64 * err) as i64;
for x in distr {
let diff = (*x as i64 - expected).abs();
assert!(diff <= thresh, "{x:?} deviates from {expected:?}");
}
}
#[test]
fn block_distribution() {
fn block_distribution_<const N: usize>() {
let filter = BloomFilter::new_builder::<N>(1000).items(random_numbers(1000, 1));
assert_even_distribution(&block_counts(&filter), 0.4);
}
block_distribution_::<512>();
block_distribution_::<256>();
block_distribution_::<128>();
block_distribution_::<64>();
}
#[test]
fn block_hash_distribution() {
fn block_hash_distribution_<H: BuildHasher + Seeded>(num_blocks: usize) {
let mut buckets = vec![0; num_blocks];
let hasher = H::seeded(&[42; 16]);
for x in random_numbers(num_blocks * 10000, 42) {
let [h1, _] = get_orginal_hashes(&hasher, &x);
buckets[block_index(num_blocks, h1)] += 1;
}
assert_even_distribution(&buckets, 0.05);
}
for size in [2, 7, 10, 100] {
block_hash_distribution_::<DefaultHasher>(size);
block_hash_distribution_::<ahash::RandomState>(size);
}
}
#[test]
fn test_seeded_hash_from_hashes_depth() {
for size in [1, 10, 100, 1000] {
let mut rng = StdRng::seed_from_u64(524323);
let mut h1 = (&mut rng).gen_range(0..u64::MAX);
let h2 = (&mut rng).gen_range(0..u64::MAX);
let mut seeded_hash_counts = vec![0; size];
for _ in 0..(size * 10_000) {
let hi = u64::next_hash(&mut h1, h2);
seeded_hash_counts[(hi as usize) % size] += 1;
}
assert_even_distribution(&seeded_hash_counts, 0.05);
}
}
#[test]
fn index_hash_distribution() {
fn index_hash_distribution_<const N: usize>(thresh_pct: f64) {
let filter: BloomFilter<N> = BloomFilter::new_builder(1).seed(&0).hashes(1);
let [mut h1, h2] = get_orginal_hashes(&filter.hasher, "qwerty");
let mut counts = vec![0; N];
let iterations = 10000 * N as u64;
for _ in 0..iterations {
let bit_index = BloomFilter::<N>::bit_index(&mut h1, h2);
let index = bit_index % N;
counts[index] += 1;
}
assert_even_distribution(&counts, thresh_pct);
}
index_hash_distribution_::<512>(0.05);
index_hash_distribution_::<256>(0.05);
index_hash_distribution_::<128>(0.05);
index_hash_distribution_::<64>(0.05);
}
#[test]
fn test_hash_integration() {
fn test_hash_integration_<const N: usize, H: BuildHasher + Seeded>(thresh_pct: f64) {
fn test_with_distr_fn<
const N: usize,
H: BuildHasher + Seeded,
F: FnMut(usize) -> usize,
>(
mut f: F,
filter: &BloomFilter<N, H>,
thresh_pct: f64,
) {
let num = 2000 * N;
let mut counts = vec![0; N * filter.num_blocks()];
for val in (0..num).map(|i| f(i)) {
let [mut h1, h2] = get_orginal_hashes(&filter.hasher, &val);
let block_index = block_index(filter.num_blocks(), h1);
for _ in 0..filter.num_hashes() {
let j = BloomFilter::<N>::bit_index(&mut h1, h2);
let global = block_index * N + j;
counts[global] += 1;
}
}
assert_even_distribution(&counts, thresh_pct);
}
for num_hashes in [1, 4, 8] {
let clone_me = BloomFilter::new_builder::<N>(4)
.hasher(H::seeded(&[42; 16]))
.hashes(num_hashes);
let mut rng = StdRng::seed_from_u64(42);
test_with_distr_fn(
|_| (&mut rng).gen_range(0..usize::MAX),
&clone_me,
thresh_pct,
);
test_with_distr_fn(|x| x * 2, &clone_me, thresh_pct);
test_with_distr_fn(|x| x * 3, &clone_me, thresh_pct);
test_with_distr_fn(
|x| x * clone_me.num_hashes() as usize,
&clone_me,
thresh_pct,
);
test_with_distr_fn(
|x| x * clone_me.num_blocks() as usize,
&clone_me,
thresh_pct,
);
test_with_distr_fn(|x| x * N, &clone_me, thresh_pct);
}
}
let pct = 0.1;
test_hash_integration_::<512, DefaultHasher>(pct);
test_hash_integration_::<256, DefaultHasher>(pct);
test_hash_integration_::<128, DefaultHasher>(pct);
test_hash_integration_::<64, DefaultHasher>(pct);
}
#[test]
fn test_debug() {
let filter = BloomFilter::with_num_bits(1).hashes(1);
assert!(!format!("{:?}", filter).is_empty());
}
#[test]
fn test_clone() {
let filter = BloomFilter::with_num_bits(4).hashes(4);
assert_eq!(filter, filter.clone());
}
#[test]
fn eq_constructors_num_bits() {
assert_eq!(
BloomFilter::with_num_bits(4).block_size_512().hashes(4),
BloomFilter::new_builder::<512>(4).hashes(4),
);
assert_eq!(
BloomFilter::with_num_bits(4).block_size_256().hashes(4),
BloomFilter::new_builder::<256>(4).hashes(4),
);
assert_eq!(
BloomFilter::with_num_bits(4).block_size_128().hashes(4),
BloomFilter::new_builder::<128>(4).hashes(4),
);
assert_eq!(
BloomFilter::with_num_bits(4).block_size_64().hashes(4),
BloomFilter::new_builder::<64>(4).hashes(4),
);
}
#[test]
fn eq_constructors_false_pos() {
assert_eq!(
BloomFilter::with_false_pos(0.4).block_size_512(),
BloomFilter::new_with_false_pos::<512>(0.4),
);
assert_eq!(
BloomFilter::with_false_pos(0.4).block_size_256(),
BloomFilter::new_with_false_pos::<256>(0.4),
);
assert_eq!(
BloomFilter::with_false_pos(0.4).block_size_128(),
BloomFilter::new_with_false_pos::<128>(0.4),
);
assert_eq!(
BloomFilter::with_false_pos(0.4).block_size_64(),
BloomFilter::new_with_false_pos::<64>(0.4),
);
}
#[test]
fn eq_constructors_from_vec() {
assert_eq!(
BloomFilter::from_vec(vec![42; 42]).block_size_512(),
BloomFilter::new_from_vec::<512>(vec![42; 42]),
);
assert_eq!(
BloomFilter::from_vec(vec![42; 42]).block_size_256(),
BloomFilter::new_from_vec::<256>(vec![42; 42]),
);
assert_eq!(
BloomFilter::from_vec(vec![42; 42]).block_size_128(),
BloomFilter::new_from_vec::<128>(vec![42; 42]),
);
assert_eq!(
BloomFilter::from_vec(vec![42; 42]).block_size_64(),
BloomFilter::new_from_vec::<64>(vec![42; 42]),
);
}
#[test]
fn test_rebuilt_from_vec() {
for num in [1, 10, 1000, 100_000] {
for fp in [0.1, 0.01, 0.0001, 0.0000001] {
let items = random_numbers(num, 42);
let b = BloomFilter::with_false_pos(fp)
.seed(&42)
.items(items.iter());
let orig_hashes = b.num_hashes();
let new = BloomFilter::from_vec(b.as_slice().to_vec())
.seed(&42)
.hashes(orig_hashes);
assert!(items.iter().all(|x| new.contains(x)));
}
}
}
}