use xxhash_rust::xxh64::xxh64;
#[derive(Clone)]
pub struct BloomFilter {
bits: Vec<u64>,
k: usize,
m: usize,
n: usize,
}
impl BloomFilter {
pub fn new(n: usize, fpr: f64) -> Self {
assert!(n > 0, "Expected number of elements must be > 0");
assert!(
fpr > 0.0 && fpr < 1.0,
"False positive rate must be in (0, 1)"
);
let m = (-(n as f64) * fpr.ln() / (std::f64::consts::LN_2.powi(2))).ceil() as usize;
let k = ((m as f64 / n as f64) * std::f64::consts::LN_2).ceil() as usize;
let k = k.max(1);
let num_words = m.div_ceil(64);
Self {
bits: vec![0u64; num_words],
k,
m,
n,
}
}
pub fn with_params(n: usize, m: usize, k: usize) -> Self {
assert!(n > 0, "Expected number of elements must be > 0");
assert!(m > 0, "Number of bits must be > 0");
assert!(k > 0, "Number of hash functions must be > 0");
let num_words = m.div_ceil(64);
Self {
bits: vec![0u64; num_words],
k,
m,
n,
}
}
#[inline(always)]
fn base_hashes(&self, key: &[u8]) -> (u64, u64) {
let h1 = xxh64(key, 0);
let h2 = xxh64(key, 1);
(h1, h2)
}
#[inline(always)]
fn fast_range(hash: u64, range: usize) -> usize {
(((hash as u128) * (range as u128)) >> 64) as usize
}
#[inline]
pub fn insert(&mut self, key: &[u8]) {
let (h1, h2) = self.base_hashes(key);
let m = self.m;
for i in 0..self.k {
let combined = h1.wrapping_add((i as u64).wrapping_mul(h2));
let bit_index = Self::fast_range(combined, m);
let word_index = bit_index / 64;
let bit_offset = bit_index % 64;
unsafe {
*self.bits.get_unchecked_mut(word_index) |= 1u64 << bit_offset;
}
}
}
#[inline]
pub fn contains(&self, key: &[u8]) -> bool {
let (h1, h2) = self.base_hashes(key);
let m = self.m;
for i in 0..self.k {
let combined = h1.wrapping_add((i as u64).wrapping_mul(h2));
let bit_index = Self::fast_range(combined, m);
let word_index = bit_index / 64;
let bit_offset = bit_index % 64;
let word = unsafe { *self.bits.get_unchecked(word_index) };
if word & (1u64 << bit_offset) == 0 {
return false;
}
}
true
}
pub fn clear(&mut self) {
self.bits.fill(0);
}
pub fn count_bits(&self) -> usize {
self.bits
.iter()
.map(|word| word.count_ones() as usize)
.sum()
}
pub fn false_positive_rate(&self) -> f64 {
let bits_set = self.count_bits() as f64 / self.m as f64;
bits_set.powi(self.k as i32)
}
pub fn memory_usage(&self) -> usize {
self.bits.len() * 8 }
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.n.to_le_bytes());
bytes.extend_from_slice(&self.m.to_le_bytes());
bytes.extend_from_slice(&self.k.to_le_bytes());
for word in &self.bits {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
if bytes.len() < 24 {
return Err("Insufficient bytes for header");
}
let n = usize::from_le_bytes(bytes[0..8].try_into().unwrap());
let m = usize::from_le_bytes(bytes[8..16].try_into().unwrap());
let k = usize::from_le_bytes(bytes[16..24].try_into().unwrap());
let num_words = m.div_ceil(64);
let expected_size = 24 + num_words * 8;
if bytes.len() != expected_size {
return Err("Invalid byte array size");
}
let mut bits = Vec::with_capacity(num_words);
for i in 0..num_words {
let offset = 24 + i * 8;
let word = u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap());
bits.push(word);
}
Ok(Self { bits, k, m, n })
}
pub fn params(&self) -> (usize, usize, usize) {
(self.n, self.m, self.k)
}
pub fn is_empty(&self) -> bool {
self.count_bits() == 0
}
pub fn len(&self) -> usize {
let fill_ratio = self.count_bits() as f64 / self.m as f64;
if fill_ratio >= 1.0 {
return self.n;
}
if fill_ratio <= 0.0 {
return 0;
}
let estimate = -(self.m as f64) * (1.0 - fill_ratio).ln() / self.k as f64;
estimate.round() as usize
}
pub fn merge(&mut self, other: &Self) {
assert_eq!(
self.bits.len(),
other.bits.len(),
"Bloom filters must have same size to merge"
);
for (a, b) in self.bits.iter_mut().zip(other.bits.iter()) {
*a |= *b;
}
}
}
impl std::fmt::Debug for BloomFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BloomFilter")
.field("n", &self.n)
.field("m", &self.m)
.field("k", &self.k)
.field("bits_set", &self.count_bits())
.field(
"fpr",
&format!("{:.4}%", self.false_positive_rate() * 100.0),
)
.field("memory_bytes", &self.memory_usage())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let filter = BloomFilter::new(1000, 0.01);
let (n, m, k) = filter.params();
assert_eq!(n, 1000);
assert!(m > 0, "Number of bits should be > 0");
assert!(k > 0, "Number of hash functions should be > 0");
}
#[test]
fn test_insert_and_contains() {
let mut filter = BloomFilter::new(100, 0.01);
filter.insert(b"key1");
filter.insert(b"key2");
filter.insert(b"key3");
assert!(filter.contains(b"key1"));
assert!(filter.contains(b"key2"));
assert!(filter.contains(b"key3"));
}
#[test]
fn test_no_false_negatives() {
let mut filter = BloomFilter::new(1000, 0.01);
let keys: Vec<Vec<u8>> = (0..1000)
.map(|i| format!("key{}", i).into_bytes())
.collect();
for key in &keys {
filter.insert(key);
}
for key in &keys {
assert!(
filter.contains(key),
"False negative for {:?}",
String::from_utf8_lossy(key)
);
}
}
#[test]
fn test_false_positive_rate() {
let mut filter = BloomFilter::new(1000, 0.01);
let keys: Vec<Vec<u8>> = (0..1000)
.map(|i| format!("key{}", i).into_bytes())
.collect();
for key in &keys {
filter.insert(key);
}
let test_keys: Vec<Vec<u8>> = (10000..20000)
.map(|i| format!("test{}", i).into_bytes())
.collect();
let false_positives = test_keys.iter().filter(|key| filter.contains(key)).count();
let actual_fpr = false_positives as f64 / test_keys.len() as f64;
assert!(actual_fpr < 0.03, "FPR too high: {:.4}", actual_fpr);
}
#[test]
fn test_empty_filter() {
let filter = BloomFilter::new(100, 0.01);
assert!(!filter.contains(b"key1"));
assert!(!filter.contains(b"key2"));
assert!(!filter.contains(b"any_key"));
}
#[test]
fn test_clear() {
let mut filter = BloomFilter::new(100, 0.01);
filter.insert(b"key1");
filter.insert(b"key2");
assert!(filter.contains(b"key1"));
filter.clear();
assert!(!filter.contains(b"key1"));
assert!(!filter.contains(b"key2"));
assert_eq!(filter.count_bits(), 0);
}
#[test]
fn test_serialization() {
let mut filter = BloomFilter::new(100, 0.01);
filter.insert(b"key1");
filter.insert(b"key2");
filter.insert(b"key3");
let bytes = filter.to_bytes();
let deserialized = BloomFilter::from_bytes(&bytes).unwrap();
assert_eq!(filter.params(), deserialized.params());
assert!(deserialized.contains(b"key1"));
assert!(deserialized.contains(b"key2"));
assert!(deserialized.contains(b"key3"));
assert!(!deserialized.contains(b"key4"));
}
#[test]
fn test_serialization_empty() {
let filter = BloomFilter::new(100, 0.01);
let bytes = filter.to_bytes();
let deserialized = BloomFilter::from_bytes(&bytes).unwrap();
assert_eq!(filter.params(), deserialized.params());
assert!(!deserialized.contains(b"any_key"));
}
#[test]
fn test_with_params() {
let filter = BloomFilter::with_params(1000, 10000, 7);
let (n, m, k) = filter.params();
assert_eq!(n, 1000);
assert_eq!(m, 10000);
assert_eq!(k, 7);
}
#[test]
fn test_binary_keys() {
let mut filter = BloomFilter::new(100, 0.01);
let binary_keys = vec![vec![0u8, 1, 2, 3], vec![255, 254, 253], vec![0, 0, 0, 0]];
for key in &binary_keys {
filter.insert(key);
}
for key in &binary_keys {
assert!(filter.contains(key));
}
}
#[test]
fn test_large_keys() {
let mut filter = BloomFilter::new(100, 0.01);
let large_key = vec![42u8; 10000];
filter.insert(&large_key);
assert!(filter.contains(&large_key));
}
#[test]
fn test_memory_usage() {
let filter = BloomFilter::new(1000, 0.01);
let memory = filter.memory_usage();
assert!(memory > 0);
assert_eq!(memory, filter.bits.len() * 8);
}
#[test]
fn test_count_bits() {
let mut filter = BloomFilter::new(100, 0.01);
assert_eq!(filter.count_bits(), 0);
filter.insert(b"key1");
let bits_after_one = filter.count_bits();
assert!(bits_after_one > 0);
filter.insert(b"key2");
let bits_after_two = filter.count_bits();
assert!(bits_after_two >= bits_after_one);
}
#[test]
#[should_panic(expected = "Expected number of elements must be > 0")]
fn test_new_panics_on_zero_n() {
BloomFilter::new(0, 0.01);
}
#[test]
#[should_panic(expected = "False positive rate must be in (0, 1)")]
fn test_new_panics_on_invalid_fpr() {
BloomFilter::new(100, 1.5);
}
#[test]
fn test_debug_format() {
let mut filter = BloomFilter::new(1000, 0.01);
filter.insert(b"test");
let debug_str = format!("{:?}", filter);
assert!(debug_str.contains("BloomFilter"));
assert!(debug_str.contains("n"));
assert!(debug_str.contains("m"));
assert!(debug_str.contains("k"));
}
}