#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_sign_loss)]
use crate::core::filter::BloomFilter;
use crate::error::{BloomCraftError, Result};
use crate::core::params::{optimal_bit_count, optimal_hash_count};
use crate::hash::{BloomHasher, StdHasher};
use std::hash::Hash;
use std::marker::PhantomData;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ClassicBitsFilter<T, H = StdHasher>
where
H: BloomHasher + Clone,
{
bits: Vec<u64>,
m: usize,
k: usize,
#[cfg_attr(feature = "serde", serde(skip))]
hasher: H,
#[cfg_attr(feature = "serde", serde(skip))]
_phantom: PhantomData<T>,
}
impl<T, H> Clone for ClassicBitsFilter<T, H>
where
T: Hash,
H: BloomHasher + Clone,
{
fn clone(&self) -> Self {
Self {
bits: self.bits.clone(),
m: self.m,
k: self.k,
hasher: self.hasher.clone(),
_phantom: PhantomData,
}
}
}
impl<T> ClassicBitsFilter<T, StdHasher>
where
T: Hash,
{
#[must_use]
pub fn new(m: usize, k: usize) -> Self {
Self::with_hasher(m, k, StdHasher::new())
}
#[must_use]
pub fn with_fpr(expected_items: usize, fpr: f64) -> Self {
assert!(expected_items > 0, "expected_items must be > 0");
assert!(fpr > 0.0 && fpr < 1.0, "fpr must be in range (0, 1), got {fpr}");
let m = optimal_bit_count(expected_items, fpr)
.expect("optimal_bit_count should succeed with valid parameters");
let k = optimal_hash_count(m, expected_items)
.expect("optimal_hash_count should succeed with valid parameters");
Self::new(m, k)
}
}
impl<T, H> ClassicBitsFilter<T, H>
where
T: Hash,
H: BloomHasher + Clone,
{
#[must_use]
pub fn with_hasher(m: usize, k: usize, hasher: H) -> Self {
assert!(m > 0, "m must be > 0");
assert!(k > 0, "k must be > 0");
let word_count = m.div_ceil(64);
Self {
bits: vec![0u64; word_count],
m,
k,
hasher,
_phantom: PhantomData,
}
}
#[must_use]
#[inline]
pub fn size(&self) -> usize {
self.m
}
#[must_use]
#[inline]
pub fn hash_count(&self) -> usize {
self.k
}
#[inline]
fn set_bit(&mut self, index: usize) {
debug_assert!(index < self.m, "Bit index {} out of bounds (m={})", index, self.m);
let word_idx = index / 64;
let bit_offset = index % 64;
let mask = 1u64 << bit_offset;
self.bits[word_idx] |= mask;
}
#[inline]
fn test_bit(&self, index: usize) -> bool {
debug_assert!(index < self.m, "Bit index {} out of bounds (m={})", index, self.m);
let word_idx = index / 64;
let bit_offset = index % 64;
let mask = 1u64 << bit_offset;
(self.bits[word_idx] & mask) != 0
}
#[inline]
fn compute_independent_hash(&self, item: &T, i: usize) -> usize {
let (h1, _) = self.hasher.hash_item(item);
let base_bytes = h1.to_le_bytes();
let index_bytes = i.to_le_bytes();
let mut combined = [0u8; 16];
combined[0..8].copy_from_slice(&base_bytes);
combined[8..16].copy_from_slice(&index_bytes);
let (h, _) = self.hasher.hash_bytes_pair(&combined);
(h as usize) % self.m
}
#[inline]
pub fn insert(&mut self, item: &T) {
for i in 0..self.k {
let index = self.compute_independent_hash(item, i);
self.set_bit(index);
}
}
#[must_use]
#[inline]
pub fn contains(&self, item: &T) -> bool {
for i in 0..self.k {
let index = self.compute_independent_hash(item, i);
if !self.test_bit(index) {
return false; }
}
true
}
pub fn clear(&mut self) {
for word in &mut self.bits {
*word = 0;
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.bits.iter().all(|&word| word == 0)
}
#[must_use]
pub fn count_set_bits(&self) -> usize {
self.bits.iter().map(|word| word.count_ones() as usize).sum()
}
#[must_use]
pub fn fill_rate(&self) -> f64 {
self.count_set_bits() as f64 / self.m as f64
}
#[must_use]
pub fn estimate_fpr(&self) -> f64 {
let fill_rate = self.fill_rate();
if fill_rate == 0.0 {
return 0.0;
}
if fill_rate >= 1.0 {
return 1.0;
}
let m_f64 = self.m as f64;
let k_f64 = self.k as f64;
let estimated_n = -(m_f64 / k_f64) * (1.0 - fill_rate).ln();
let exponent = -(k_f64 * estimated_n) / m_f64;
(1.0 - exponent.exp()).powf(k_f64)
}
#[must_use]
pub fn is_full(&self) -> bool {
self.fill_rate() > 0.5
}
#[must_use]
pub fn memory_usage(&self) -> usize {
let bits_mem = self.bits.len() * std::mem::size_of::<u64>();
let metadata_mem = std::mem::size_of::<Self>();
bits_mem + metadata_mem
}
pub fn insert_batch(&mut self, items: &[T]) {
for item in items {
self.insert(item);
}
}
#[must_use]
pub fn contains_batch(&self, items: &[T]) -> Vec<bool> {
items.iter().map(|item| self.contains(item)).collect()
}
pub fn union(&self, other: &Self) -> Result<Self> {
if self.m != other.m || self.k != other.k {
return Err(BloomCraftError::IncompatibleFilters {
reason: format!(
"Parameter mismatch: self(m={}, k={}) vs other(m={}, k={})",
self.m, self.k, other.m, other.k
),
});
}
let mut result = self.clone();
for (i, word) in result.bits.iter_mut().enumerate() {
*word |= other.bits[i];
}
Ok(result)
}
pub fn intersect(&self, other: &Self) -> Result<Self> {
if self.m != other.m || self.k != other.k {
return Err(BloomCraftError::IncompatibleFilters {
reason: format!(
"Parameter mismatch: self(m={}, k={}) vs other(m={}, k={})",
self.m, self.k, other.m, other.k
),
});
}
let mut result = self.clone();
for (i, word) in result.bits.iter_mut().enumerate() {
*word &= other.bits[i];
}
Ok(result)
}
}
impl<T, H> BloomFilter<T> for ClassicBitsFilter<T, H>
where
T: Hash + Send + Sync,
H: BloomHasher + Clone,
{
fn insert(&mut self, item: &T) {
ClassicBitsFilter::insert(self, item);
}
fn contains(&self, item: &T) -> bool {
ClassicBitsFilter::contains(self, item)
}
fn clear(&mut self) {
ClassicBitsFilter::clear(self);
}
fn len(&self) -> usize {
self.count_set_bits()
}
fn is_empty(&self) -> bool {
ClassicBitsFilter::is_empty(self)
}
fn false_positive_rate(&self) -> f64 {
self.estimate_fpr()
}
fn expected_items(&self) -> usize {
((self.m as f64 * std::f64::consts::LN_2) / self.k as f64) as usize
}
fn bit_count(&self) -> usize {
self.m
}
fn hash_count(&self) -> usize {
self.k
}
fn count_set_bits(&self) -> usize {
self.bits.iter().map(|word| word.count_ones() as usize).sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let filter: ClassicBitsFilter<&str> = ClassicBitsFilter::new(1000, 7);
assert_eq!(filter.size(), 1000);
assert_eq!(filter.hash_count(), 7);
assert!(filter.is_empty());
}
#[test]
#[should_panic(expected = "m must be > 0")]
fn test_new_zero_size() {
let _: ClassicBitsFilter<&str> = ClassicBitsFilter::new(0, 7);
}
#[test]
#[should_panic(expected = "k must be > 0")]
fn test_new_zero_k() {
let _: ClassicBitsFilter<&str> = ClassicBitsFilter::new(1000, 0);
}
#[test]
fn test_insert_and_contains() {
let mut filter = ClassicBitsFilter::new(1000, 7);
filter.insert(&"hello");
assert!(filter.contains(&"hello"));
assert!(!filter.contains(&"world"));
}
#[test]
fn test_multiple_inserts() {
let mut filter = ClassicBitsFilter::new(10_000, 7);
for i in 0..1000 {
filter.insert(&i);
}
for i in 0..1000 {
assert!(filter.contains(&i), "False negative for {}", i);
}
}
#[test]
fn test_clear() {
let mut filter = ClassicBitsFilter::new(1000, 7);
filter.insert(&"hello");
filter.insert(&"world");
assert!(!filter.is_empty());
filter.clear();
assert!(filter.is_empty());
assert!(!filter.contains(&"hello"));
assert!(!filter.contains(&"world"));
}
#[test]
fn test_count_set_bits() {
let mut filter = ClassicBitsFilter::new(1000, 7);
assert_eq!(filter.count_set_bits(), 0);
filter.insert(&"test");
let set_bits = filter.count_set_bits();
assert!(set_bits > 0);
assert!(set_bits <= 7); }
#[test]
fn test_fill_rate() {
let mut filter = ClassicBitsFilter::new(1000, 7);
assert_eq!(filter.fill_rate(), 0.0);
for i in 0..100 {
filter.insert(&i);
}
let fill_rate = filter.fill_rate();
assert!(fill_rate > 0.0 && fill_rate < 1.0);
}
#[test]
fn test_estimate_fpr() {
let mut filter = ClassicBitsFilter::new(10_000, 7);
for i in 0..5000 {
filter.insert(&i);
}
let fpr = filter.estimate_fpr();
assert!(fpr > 0.0 && fpr < 1.0);
}
#[test]
fn test_is_full() {
let mut filter = ClassicBitsFilter::new(100, 7);
for i in 0..1000 {
filter.insert(&i);
}
assert!(filter.is_full());
}
#[test]
fn test_memory_usage() {
let filter: ClassicBitsFilter<i32> = ClassicBitsFilter::new(10_000, 7);
let mem = filter.memory_usage();
assert!(mem > 0);
}
#[test]
fn test_with_fpr() {
let filter: ClassicBitsFilter<i32> = ClassicBitsFilter::with_fpr(10_000, 0.01);
assert!(filter.size() > 0);
assert!(filter.hash_count() >= 6 && filter.hash_count() <= 8);
}
#[test]
#[should_panic(expected = "expected_items must be > 0")]
fn test_with_fpr_zero_items() {
let _: ClassicBitsFilter<i32> = ClassicBitsFilter::with_fpr(0, 0.01);
}
#[test]
#[should_panic(expected = "fpr must be in range (0, 1)")]
fn test_with_fpr_invalid_fpr() {
let _: ClassicBitsFilter<i32> = ClassicBitsFilter::with_fpr(1000, 1.5);
}
#[test]
fn test_insert_batch() {
let mut filter = ClassicBitsFilter::new(1000, 7);
let items = vec!["a", "b", "c", "d"];
filter.insert_batch(&items);
for item in &items {
assert!(filter.contains(item));
}
}
#[test]
fn test_contains_batch() {
let mut filter = ClassicBitsFilter::new(1000, 7);
filter.insert(&"a");
filter.insert(&"b");
let queries = vec!["a", "b", "c", "d"];
let results = filter.contains_batch(&queries);
assert_eq!(results, vec![true, true, false, false]);
}
#[test]
fn test_union() {
let mut filter1 = ClassicBitsFilter::new(1000, 7);
let mut filter2 = ClassicBitsFilter::new(1000, 7);
filter1.insert(&"a");
filter1.insert(&"b");
filter2.insert(&"b");
filter2.insert(&"c");
let union = filter1.union(&filter2).unwrap();
assert!(union.contains(&"a"));
assert!(union.contains(&"b"));
assert!(union.contains(&"c"));
}
#[test]
fn test_union_incompatible() {
let filter1: ClassicBitsFilter<String> = ClassicBitsFilter::new(1000, 7);
let filter2: ClassicBitsFilter<String> = ClassicBitsFilter::new(2000, 7);
let result = filter1.union(&filter2);
assert!(result.is_err());
}
#[test]
fn test_intersect() {
let mut filter1 = ClassicBitsFilter::new(1000, 7);
let mut filter2 = ClassicBitsFilter::new(1000, 7);
filter1.insert(&"a");
filter1.insert(&"b");
filter2.insert(&"b");
filter2.insert(&"c");
let intersection = filter1.intersect(&filter2).unwrap();
assert!(intersection.contains(&"b"));
}
#[test]
fn test_bloom_filter_trait() {
let mut filter = ClassicBitsFilter::new(1000, 7);
BloomFilter::insert(&mut filter, &"test");
assert!(BloomFilter::contains(&filter, &"test"));
assert!(!BloomFilter::is_empty(&filter));
BloomFilter::clear(&mut filter);
assert!(BloomFilter::is_empty(&filter));
}
#[test]
fn test_no_false_negatives() {
let mut filter = ClassicBitsFilter::new(10_000, 7);
let items = vec!["apple", "banana", "cherry", "date", "elderberry"];
for item in &items {
filter.insert(item);
}
for item in &items {
assert!(filter.contains(item), "False negative for {}", item);
}
}
#[test]
fn test_false_positive_rate() {
let mut filter: ClassicBitsFilter<i32> = ClassicBitsFilter::with_fpr(1000, 0.01);
for i in 0..1000 {
filter.insert(&i);
}
let mut false_positives = 0;
for i in 1000..11_000 {
if filter.contains(&i) {
false_positives += 1;
}
}
let actual_fpr = false_positives as f64 / 10_000.0;
assert!(
actual_fpr < 0.20,
"FPR too high: {:.4} (expected < 0.20 for 1970 baseline with k independent hashes)",
actual_fpr
);
assert!(
actual_fpr < 0.50,
"FPR catastrophically high: {:.4} - filter may be broken",
actual_fpr
);
}
#[test]
fn test_duplicate_inserts() {
let mut filter = ClassicBitsFilter::new(1000, 7);
filter.insert(&"test");
let bits_after_first = filter.count_set_bits();
filter.insert(&"test");
let bits_after_second = filter.count_set_bits();
assert_eq!(bits_after_first, bits_after_second);
}
#[test]
fn test_clone() {
let mut filter1 = ClassicBitsFilter::new(1000, 7);
filter1.insert(&"test");
let filter2 = filter1.clone();
assert!(filter2.contains(&"test"));
assert_eq!(filter1.size(), filter2.size());
assert_eq!(filter1.hash_count(), filter2.hash_count());
}
#[test]
fn test_independent_hash_generation() {
let filter: ClassicBitsFilter<&str> = ClassicBitsFilter::new(10_000, 7);
let mut indices = Vec::new();
for i in 0..7 {
let index = filter.compute_independent_hash(&"test", i);
indices.push(index);
}
for &idx in &indices {
assert!(idx < 10_000);
}
let unique_count = indices.iter().collect::<std::collections::HashSet<_>>().len();
assert!(unique_count >= 5, "Too many hash collisions");
}
#[test]
fn test_bit_operations() {
let mut filter: ClassicBitsFilter<String> = ClassicBitsFilter::new(64, 3);
filter.set_bit(0);
assert!(filter.test_bit(0));
assert!(!filter.test_bit(1));
filter.set_bit(63);
assert!(filter.test_bit(63));
}
}