use bytes::{Bytes, BytesMut, BufMut};
#[derive(Clone)]
pub struct BloomFilter {
bits: Vec<u8>,
num_bits: usize,
num_hashes: u32,
}
impl BloomFilter {
pub fn new(num_items: usize, bits_per_key: usize) -> Self {
let num_bits = num_items * bits_per_key;
let num_bytes = (num_bits + 7) / 8;
let num_hashes = ((bits_per_key as f64) * 0.693).ceil() as u32;
let num_hashes = num_hashes.max(1).min(30);
Self {
bits: vec![0u8; num_bytes],
num_bits,
num_hashes,
}
}
pub fn add(&mut self, key: &[u8]) {
let hash = Self::hash(key);
for i in 0..self.num_hashes {
let bit_pos = Self::bloom_hash(hash, i) % (self.num_bits as u64);
self.set_bit(bit_pos as usize);
}
}
pub fn contains(&self, key: &[u8]) -> bool {
let hash = Self::hash(key);
for i in 0..self.num_hashes {
let bit_pos = Self::bloom_hash(hash, i) % (self.num_bits as u64);
if !self.get_bit(bit_pos as usize) {
return false; }
}
true }
pub fn encode(&self) -> Bytes {
let mut buf = BytesMut::new();
buf.put_u32_le(self.num_bits as u32);
buf.put_u32_le(self.num_hashes);
buf.put_slice(&self.bits);
buf.freeze()
}
pub fn decode(data: &[u8]) -> Option<Self> {
if data.len() < 8 {
return None;
}
let num_bits = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
let num_hashes = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
if num_bits == 0 {
return None;
}
let num_bytes = (num_bits + 7) / 8;
if data.len() < 8 + num_bytes {
return None;
}
let bits = data[8..8 + num_bytes].to_vec();
Some(Self {
bits,
num_bits,
num_hashes,
})
}
pub fn size(&self) -> usize {
8 + self.bits.len()
}
fn set_bit(&mut self, pos: usize) {
let byte_idx = pos / 8;
let bit_idx = pos % 8;
if byte_idx < self.bits.len() {
self.bits[byte_idx] |= 1 << bit_idx;
}
}
fn get_bit(&self, pos: usize) -> bool {
let byte_idx = pos / 8;
let bit_idx = pos % 8;
if byte_idx < self.bits.len() {
(self.bits[byte_idx] & (1 << bit_idx)) != 0
} else {
false
}
}
fn hash(key: &[u8]) -> u64 {
let mut hash = 0xcbf29ce484222325u64;
for &byte in key {
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001b3);
}
hash
}
fn bloom_hash(hash: u64, i: u32) -> u64 {
let h1 = hash;
let h2 = hash.wrapping_shr(32);
h1.wrapping_add((i as u64).wrapping_mul(h2))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bloom_basic() {
let mut bloom = BloomFilter::new(100, 10);
bloom.add(b"key1");
bloom.add(b"key2");
bloom.add(b"key3");
assert!(bloom.contains(b"key1"));
assert!(bloom.contains(b"key2"));
assert!(bloom.contains(b"key3"));
assert!(!bloom.contains(b"key4"));
assert!(!bloom.contains(b"key5"));
}
#[test]
fn test_bloom_encode_decode() {
let mut bloom = BloomFilter::new(50, 10);
bloom.add(b"test1");
bloom.add(b"test2");
let encoded = bloom.encode();
let decoded = BloomFilter::decode(&encoded).unwrap();
assert_eq!(decoded.num_bits, bloom.num_bits);
assert_eq!(decoded.num_hashes, bloom.num_hashes);
assert_eq!(decoded.bits, bloom.bits);
assert!(decoded.contains(b"test1"));
assert!(decoded.contains(b"test2"));
assert!(!decoded.contains(b"test3"));
}
#[test]
fn test_bloom_false_positive_rate() {
let mut bloom = BloomFilter::new(1000, 10);
for i in 0..1000 {
let key = format!("key{}", i);
bloom.add(key.as_bytes());
}
let mut false_positives = 0;
let test_count = 10000;
for i in 1000..1000 + test_count {
let key = format!("key{}", i);
if bloom.contains(key.as_bytes()) {
false_positives += 1;
}
}
let fp_rate = (false_positives as f64) / (test_count as f64);
assert!(fp_rate < 0.02, "False positive rate too high: {}", fp_rate);
}
#[test]
fn test_bloom_empty() {
let bloom = BloomFilter::new(10, 10);
assert!(!bloom.contains(b"test"));
}
#[test]
fn test_bloom_size() {
let bloom = BloomFilter::new(100, 10);
let size = bloom.size();
let expected_bytes = (100 * 10 + 7) / 8;
assert_eq!(size, 8 + expected_bytes);
}
#[test]
fn test_bloom_decode_invalid() {
assert!(BloomFilter::decode(&[0, 1, 2]).is_none());
let data = vec![0u8; 8];
assert!(BloomFilter::decode(&data).is_none());
}
#[test]
fn test_bloom_num_hashes() {
let bloom = BloomFilter::new(100, 10);
assert_eq!(bloom.num_hashes, 7);
let bloom = BloomFilter::new(100, 5);
assert_eq!(bloom.num_hashes, 4);
}
}