#![warn(clippy::all)]
#![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::hash::{BloomHasher, StdHasher};
use std::hash::Hash;
#[derive(Debug, Clone)]
pub struct ClassicHashFilter<T, H = StdHasher>
where
T: Hash + Clone + Eq,
H: BloomHasher + Clone,
{
table: Vec<Vec<T>>,
m: usize,
d: usize,
count: usize,
discarded: usize,
hasher: H,
}
impl<T> ClassicHashFilter<T, StdHasher>
where
T: Hash + Clone + Eq,
{
#[must_use]
pub fn new(m: usize, d: usize) -> Self {
Self::with_hasher(m, d, StdHasher::new())
}
#[must_use]
pub fn with_fpr(expected_items: usize, fpr: f64) -> Self {
let (m, d) = calculate_optimal_params(expected_items, fpr);
Self::new(m, d)
}
}
impl<T, H> ClassicHashFilter<T, H>
where
T: Hash + Clone + Eq,
H: BloomHasher + Clone,
{
#[must_use]
pub fn with_hasher(m: usize, d: usize, hasher: H) -> Self {
assert!(m > 0, "m (bucket count) must be > 0");
assert!(d > 0, "d (max depth) must be > 0");
Self {
table: vec![Vec::with_capacity(d.min(4)); m],
m,
d,
count: 0,
discarded: 0,
hasher,
}
}
#[must_use]
#[inline]
pub const fn bucket_count(&self) -> usize {
self.m
}
#[must_use]
#[inline]
pub const fn max_depth(&self) -> usize {
self.d
}
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
self.count
}
#[must_use]
#[inline]
pub const fn discarded_count(&self) -> usize {
self.discarded
}
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
fn hash_to_bucket(&self, item: &T) -> usize {
let (h, _) = self.hasher.hash_item(item);
(h as usize) % self.m
}
pub fn insert(&mut self, item: &T) -> bool {
let bucket_idx = self.hash_to_bucket(item);
let chain = &mut self.table[bucket_idx];
if chain.contains(item) {
return false;
}
if chain.len() < self.d {
chain.push(item.clone());
self.count += 1;
true
} else {
self.discarded += 1;
false
}
}
#[must_use]
pub fn contains(&self, item: &T) -> bool {
let bucket_idx = self.hash_to_bucket(item);
let chain = &self.table[bucket_idx];
chain.contains(item) || chain.len() == self.d
}
pub fn clear(&mut self) {
for chain in &mut self.table {
chain.clear();
}
self.count = 0;
self.discarded = 0;
}
#[must_use]
pub fn avg_chain_length(&self) -> f64 {
let non_empty = self.table.iter().filter(|c| !c.is_empty()).count();
if non_empty == 0 {
return 0.0;
}
self.count as f64 / non_empty as f64
}
#[must_use]
pub fn max_chain_length(&self) -> usize {
self.table.iter().map(Vec::len).max().unwrap_or(0)
}
#[must_use]
pub fn load_factor(&self) -> f64 {
let non_empty = self.table.iter().filter(|c| !c.is_empty()).count();
non_empty as f64 / self.m as f64
}
#[must_use]
pub fn estimate_fpr(&self) -> f64 {
if self.count == 0 {
return 0.0;
}
let full_buckets = self.table.iter().filter(|chain| chain.len() == self.d).count();
full_buckets as f64 / self.m as f64
}
#[must_use]
pub fn memory_usage(&self) -> usize {
let bucket_overhead = self.m * std::mem::size_of::<Vec<T>>();
let element_storage: usize = self
.table
.iter()
.map(|chain| chain.capacity() * std::mem::size_of::<T>())
.sum();
let metadata = std::mem::size_of::<Self>();
bucket_overhead + element_storage + metadata
}
pub fn insert_batch(&mut self, items: &[T]) -> usize {
let mut inserted = 0;
for item in items {
if self.insert(item) {
inserted += 1;
}
}
inserted
}
#[must_use]
pub fn contains_batch(&self, items: &[T]) -> Vec<bool> {
items.iter().map(|item| self.contains(item)).collect()
}
pub fn elements(&self) -> impl Iterator<Item = &T> {
self.table.iter().flat_map(|chain| chain.iter())
}
#[must_use]
pub fn is_saturated(&self) -> bool {
let total_attempts = self.count + self.discarded;
if total_attempts == 0 {
return false;
}
(self.discarded as f64 / total_attempts as f64) > 0.2
}
}
impl<T, H> BloomFilter<T> for ClassicHashFilter<T, H>
where
T: Hash + Clone + Eq + Send + Sync,
H: BloomHasher + Clone,
{
fn insert(&mut self, item: &T) {
ClassicHashFilter::insert(self, item);
}
fn contains(&self, item: &T) -> bool {
ClassicHashFilter::contains(self, item)
}
fn clear(&mut self) {
ClassicHashFilter::clear(self);
}
fn len(&self) -> usize {
ClassicHashFilter::len(self)
}
fn is_empty(&self) -> bool {
ClassicHashFilter::is_empty(self)
}
fn false_positive_rate(&self) -> f64 {
self.estimate_fpr()
}
fn expected_items(&self) -> usize {
self.count
}
fn bit_count(&self) -> usize {
self.m * self.d * std::mem::size_of::<T>() * 8
}
fn hash_count(&self) -> usize {
1
}
fn count_set_bits(&self) -> usize {
self.count * std::mem::size_of::<T>() * 8
}
}
fn calculate_optimal_params(n: usize, fpr: f64) -> (usize, usize) {
assert!(fpr > 0.0 && fpr < 1.0, "fpr must be in (0, 1)");
assert!(n > 0, "n must be > 0");
let d = 3;
let lambda = if fpr < 0.01 {
-fpr.ln() / 2.0
} else {
(n as f64 * 0.1 / fpr).sqrt().max(1.0)
};
let m = ((n as f64) / lambda).ceil() as usize;
(m.max(1), d)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let filter: ClassicHashFilter<&str> = ClassicHashFilter::new(1000, 3);
assert_eq!(filter.bucket_count(), 1000);
assert_eq!(filter.max_depth(), 3);
assert_eq!(filter.len(), 0);
assert!(filter.is_empty());
}
#[test]
#[should_panic(expected = "m (bucket count) must be > 0")]
fn test_new_zero_buckets() {
let _: ClassicHashFilter<&str> = ClassicHashFilter::new(0, 3);
}
#[test]
#[should_panic(expected = "d (max depth) must be > 0")]
fn test_new_zero_depth() {
let _: ClassicHashFilter<&str> = ClassicHashFilter::new(1000, 0);
}
#[test]
fn test_insert_and_contains() {
let mut filter = ClassicHashFilter::new(100, 3);
assert!(filter.insert(&"hello"));
assert!(filter.contains(&"hello"));
assert!(!filter.contains(&"world"));
}
#[test]
fn test_duplicate_insert() {
let mut filter = ClassicHashFilter::new(100, 3);
assert!(filter.insert(&"test"));
assert!(!filter.insert(&"test")); assert_eq!(filter.len(), 1);
}
#[test]
fn test_chain_overflow() {
let mut filter = ClassicHashFilter::new(1, 2);
filter.insert(&0);
filter.insert(&1);
let _initial_count = filter.len();
let initial_discarded = filter.discarded_count();
for i in 2..10 {
filter.insert(&i);
}
assert!(filter.len() <= 2);
assert!(filter.discarded_count() >= initial_discarded);
}
#[test]
fn test_overflow_contains_no_false_negative() {
let mut filter = ClassicHashFilter::new(1, 2);
assert!(filter.insert(&1));
assert!(filter.insert(&2));
assert_eq!(filter.len(), 2);
assert!(!filter.insert(&3));
assert_eq!(filter.discarded_count(), 1);
assert!(filter.contains(&3));
}
#[test]
fn test_clear() {
let mut filter = ClassicHashFilter::new(100, 3);
filter.insert(&"a");
filter.insert(&"b");
assert_eq!(filter.len(), 2);
filter.clear();
assert_eq!(filter.len(), 0);
assert!(filter.is_empty());
assert!(!filter.contains(&"a"));
assert_eq!(filter.discarded_count(), 0);
}
#[test]
fn test_avg_chain_length() {
let mut filter = ClassicHashFilter::new(100, 5);
for i in 0..50 {
filter.insert(&i);
}
let avg = filter.avg_chain_length();
assert!(avg > 0.0);
assert!(avg <= 5.0); }
#[test]
fn test_max_chain_length() {
let mut filter = ClassicHashFilter::new(100, 5);
for i in 0..50 {
filter.insert(&i);
}
let max_len = filter.max_chain_length();
assert!(max_len > 0);
assert!(max_len <= 5); }
#[test]
fn test_load_factor() {
let mut filter = ClassicHashFilter::new(100, 3);
assert_eq!(filter.load_factor(), 0.0);
for i in 0..20 {
filter.insert(&i);
}
let load = filter.load_factor();
assert!(load > 0.0 && load <= 1.0);
}
#[test]
fn test_estimate_fpr() {
let mut filter = ClassicHashFilter::new(1000, 3);
for i in 0..500 {
filter.insert(&i);
}
let fpr = filter.estimate_fpr();
assert!(fpr >= 0.0 && fpr < 1.0);
}
#[test]
fn test_memory_usage() {
let filter: ClassicHashFilter<i32> = ClassicHashFilter::new(1000, 3);
let mem = filter.memory_usage();
assert!(mem > 0);
}
#[test]
fn test_with_fpr() {
let filter: ClassicHashFilter<i32> = ClassicHashFilter::with_fpr(1000, 0.01);
assert!(filter.bucket_count() > 0);
assert!(filter.max_depth() > 0);
}
#[test]
fn test_insert_batch() {
let mut filter = ClassicHashFilter::new(100, 3);
let items = vec!["a", "b", "c", "d"];
let inserted = filter.insert_batch(&items);
assert_eq!(inserted, 4);
for item in &items {
assert!(filter.contains(item));
}
}
#[test]
fn test_contains_batch() {
let mut filter = ClassicHashFilter::new(100, 3);
filter.insert(&"a");
filter.insert(&"b");
let queries = vec!["a", "b", "c", "d"];
let results = filter.contains_batch(&queries);
assert_eq!(results[0], true);
assert_eq!(results[1], true);
assert_eq!(results[2], false);
assert_eq!(results[3], false);
}
#[test]
fn test_elements_iterator() {
let mut filter = ClassicHashFilter::new(100, 3);
filter.insert(&"hello");
filter.insert(&"world");
filter.insert(&"rust");
let elements: Vec<_> = filter.elements().collect();
assert_eq!(elements.len(), 3);
assert!(elements.contains(&&"hello"));
assert!(elements.contains(&&"world"));
assert!(elements.contains(&&"rust"));
}
#[test]
fn test_is_saturated() {
let mut filter = ClassicHashFilter::new(2, 1);
assert!(!filter.is_saturated());
for i in 0..20 {
filter.insert(&i);
}
assert!(filter.is_saturated());
}
#[test]
fn test_bloom_filter_trait() {
let mut filter = ClassicHashFilter::new(100, 3);
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 = ClassicHashFilter::new(100, 3);
let items = vec!["apple", "banana", "cherry"];
for item in &items {
filter.insert(item);
}
for item in &items {
assert!(filter.contains(item), "False negative for {}", item);
}
}
#[test]
fn test_calculate_params() {
let (m, d) = calculate_optimal_params(1000, 0.01);
assert!(m > 0);
assert!(d > 0);
}
#[test]
fn test_clone() {
let mut filter1 = ClassicHashFilter::new(100, 3);
filter1.insert(&"test");
let filter2 = filter1.clone();
assert!(filter2.contains(&"test"));
assert_eq!(filter1.len(), filter2.len());
}
#[test]
fn test_discarded_tracking() {
let mut filter = ClassicHashFilter::new(1, 1);
filter.insert(&1);
assert_eq!(filter.discarded_count(), 0);
for i in 2..10 {
filter.insert(&i);
}
assert!(filter.len() + filter.discarded_count() > 1);
}
}