use crate::common::{hash::xxhash, Sketch, SketchError};
use std::collections::HashSet;
const MAX_ITERATIONS: u64 = 10000;
#[derive(Debug, Clone)]
pub struct BinaryFuseFilter {
seed: u64,
segment_length: u32,
segment_count: u32,
fingerprints: Vec<u8>,
size: usize,
bits_per_entry: u8,
}
impl BinaryFuseFilter {
pub fn from_items<I>(items: I, bits_per_entry: u8) -> Result<Self, SketchError>
where
I: IntoIterator<Item = u64>,
{
if !(8..=16).contains(&bits_per_entry) {
return Err(SketchError::InvalidParameter {
param: "bits_per_entry".to_string(),
value: bits_per_entry.to_string(),
constraint: "must be in range [8, 16]".to_string(),
});
}
let unique_items: Vec<u64> = items
.into_iter()
.collect::<HashSet<_>>()
.into_iter()
.collect();
let size = unique_items.len();
if size == 0 {
return Ok(Self {
seed: 0,
segment_length: 0,
segment_count: 3,
fingerprints: Vec::new(),
size: 0,
bits_per_entry,
});
}
let segment_length = Self::calculate_segment_length(size);
let segment_count = 3u32;
let array_length = (segment_length * segment_count) as usize;
for seed in 0..MAX_ITERATIONS {
if let Some(fingerprints) = Self::try_construct(
&unique_items,
seed,
segment_length,
array_length,
bits_per_entry,
) {
return Ok(Self {
seed,
segment_length,
segment_count,
fingerprints,
size,
bits_per_entry,
});
}
}
Err(SketchError::SerializationError(
"Failed to construct Binary Fuse Filter after max iterations".to_string(),
))
}
fn calculate_segment_length(size: usize) -> u32 {
if size == 0 {
return 0;
}
let segment_length_f64 = (size as f64 * 1.23 / 3.0).ceil();
(segment_length_f64 as u32).max(4) }
fn try_construct(
items: &[u64],
seed: u64,
segment_length: u32,
array_length: usize,
bits_per_entry: u8,
) -> Option<Vec<u8>> {
if items.is_empty() {
return Some(Vec::new());
}
let mut fingerprints = vec![0u8; array_length];
let mut h0_h1_h2: Vec<(u32, u32, u32)> = Vec::with_capacity(items.len());
let mut target_fps: Vec<u8> = Vec::with_capacity(items.len());
let mut reverse_order: Vec<Vec<u32>> = vec![Vec::new(); array_length];
let mut t2count = vec![0u32; array_length];
for (item_idx, &item) in items.iter().enumerate() {
let (h0, h1, h2) = Self::hash_to_indices(item, seed, segment_length);
let fingerprint = Self::get_fingerprint(item, bits_per_entry);
h0_h1_h2.push((h0, h1, h2));
target_fps.push(fingerprint);
reverse_order[h0 as usize].push(item_idx as u32);
reverse_order[h1 as usize].push(item_idx as u32);
reverse_order[h2 as usize].push(item_idx as u32);
t2count[h0 as usize] += 1;
t2count[h1 as usize] += 1;
t2count[h2 as usize] += 1;
}
let mut alone: Vec<u32> = Vec::with_capacity(array_length);
for (i, &count) in t2count.iter().enumerate().take(array_length) {
if count == 1 {
alone.push(i as u32);
}
}
let mut stack: Vec<(u32, u8)> = Vec::with_capacity(items.len());
let mut peeled = vec![false; items.len()];
while let Some(pos) = alone.pop() {
let pos_usize = pos as usize;
if t2count[pos_usize] == 0 {
continue; }
let mut found_item = None;
for &item_idx in &reverse_order[pos_usize] {
let item_idx_usize = item_idx as usize;
if !peeled[item_idx_usize] {
found_item = Some(item_idx_usize);
break;
}
}
if let Some(item_idx) = found_item {
let (h0, h1, h2) = h0_h1_h2[item_idx];
let alone_position = if pos == h0 {
0u8
} else if pos == h1 {
1u8
} else {
2u8
};
stack.push((item_idx as u32, alone_position));
peeled[item_idx] = true;
for &h in &[h0, h1, h2] {
let h_usize = h as usize;
if t2count[h_usize] > 0 {
t2count[h_usize] -= 1;
if t2count[h_usize] == 1 {
alone.push(h);
}
}
}
}
}
if stack.len() != items.len() {
return None; }
let mut assigned = vec![false; array_length];
while let Some((item_idx, alone_position)) = stack.pop() {
let item_idx = item_idx as usize;
let (h0, h1, h2) = h0_h1_h2[item_idx];
let target_fp = target_fps[item_idx];
let indices = [h0, h1, h2];
let alone_idx = indices[alone_position as usize] as usize;
let mut xor_result = 0u8;
for (i, &idx) in indices.iter().enumerate() {
if i != alone_position as usize {
xor_result ^= fingerprints[idx as usize];
}
}
fingerprints[alone_idx] = target_fp ^ xor_result;
assigned[alone_idx] = true;
}
Some(fingerprints)
}
fn hash_to_indices(item: u64, seed: u64, segment_length: u32) -> (u32, u32, u32) {
let mixed = item ^ seed;
let item_bytes = mixed.to_le_bytes();
let h = xxhash(&item_bytes, seed);
let h0_raw = h.wrapping_mul(0x9E3779B97F4A7C15);
let h1_raw = h.wrapping_mul(0xBF58476D1CE4E5B9);
let h2_raw = h.wrapping_mul(0x94D049BB133111EB);
let h0_mixed = (h0_raw ^ (h0_raw >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
let h1_mixed = (h1_raw ^ (h1_raw >> 27)).wrapping_mul(0x94D049BB133111EB);
let h2_mixed = (h2_raw ^ (h2_raw >> 31)).wrapping_mul(0x9E3779B97F4A7C15);
let h0 = (h0_mixed as u32) % segment_length;
let h1 = segment_length + ((h1_mixed as u32) % segment_length);
let h2 = 2 * segment_length + ((h2_mixed as u32) % segment_length);
(h0, h1, h2)
}
fn get_fingerprint(item: u64, bits_per_entry: u8) -> u8 {
let mixed = item.wrapping_mul(0x9E3779B97F4A7C15);
if bits_per_entry >= 8 {
let shift = 64 - bits_per_entry as u32;
(mixed >> shift) as u8
} else {
let mask = ((1u16 << bits_per_entry) - 1) as u8;
(mixed as u8) & mask
}
}
pub fn contains(&self, item: &u64) -> bool {
if self.is_empty() {
return false;
}
let (h0, h1, h2) = Self::hash_to_indices(*item, self.seed, self.segment_length);
let expected_fp = Self::get_fingerprint(*item, self.bits_per_entry);
let actual_fp = self.fingerprints[h0 as usize]
^ self.fingerprints[h1 as usize]
^ self.fingerprints[h2 as usize];
actual_fp == expected_fp
}
pub fn len(&self) -> usize {
self.size
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn bits_per_entry(&self) -> f64 {
if self.size == 0 {
return 0.0;
}
(self.fingerprints.len() * 8) as f64 / self.size as f64
}
pub fn estimated_fpr(&self) -> f64 {
2.0_f64.powf(-(self.bits_per_entry as f64))
}
}
impl Sketch for BinaryFuseFilter {
type Item = u64;
fn update(&mut self, _item: &Self::Item) {
panic!("BinaryFuseFilter is immutable - use from_items() to build");
}
fn estimate(&self) -> f64 {
self.len() as f64
}
fn is_empty(&self) -> bool {
self.is_empty()
}
fn serialize(&self) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.seed.to_le_bytes());
bytes.extend_from_slice(&self.segment_length.to_le_bytes());
bytes.extend_from_slice(&self.segment_count.to_le_bytes());
bytes.extend_from_slice(&self.size.to_le_bytes());
bytes.push(self.bits_per_entry);
bytes.extend_from_slice(&self.fingerprints);
bytes
}
fn deserialize(bytes: &[u8]) -> Result<Self, SketchError> {
if bytes.len() < 25 {
return Err(SketchError::DeserializationError(
"Insufficient bytes for Binary Fuse Filter header".to_string(),
));
}
let seed = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
let segment_length = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
let segment_count = u32::from_le_bytes(bytes[12..16].try_into().unwrap());
let size = usize::from_le_bytes(bytes[16..24].try_into().unwrap());
let bits_per_entry = bytes[24];
if segment_count != 3 {
return Err(SketchError::DeserializationError(format!(
"Invalid segment count: expected 3, got {}",
segment_count
)));
}
if !(8..=16).contains(&bits_per_entry) {
return Err(SketchError::DeserializationError(format!(
"Invalid bits_per_entry: {}",
bits_per_entry
)));
}
let fingerprints = bytes[25..].to_vec();
let expected_fp_len = (segment_length * segment_count) as usize;
if fingerprints.len() != expected_fp_len && size > 0 {
return Err(SketchError::DeserializationError(format!(
"Fingerprint array length mismatch: expected {}, got {}",
expected_fp_len,
fingerprints.len()
)));
}
Ok(Self {
seed,
segment_length,
segment_count,
fingerprints,
size,
bits_per_entry,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_segment_length() {
assert_eq!(BinaryFuseFilter::calculate_segment_length(0), 0);
assert_eq!(BinaryFuseFilter::calculate_segment_length(1), 4);
let seg_len = BinaryFuseFilter::calculate_segment_length(100);
assert!((40..=45).contains(&seg_len));
}
#[test]
fn test_hash_to_indices() {
let (h0, h1, h2) = BinaryFuseFilter::hash_to_indices(42, 0, 64);
assert!(h0 < 64);
assert!((64..128).contains(&h1));
assert!((128..192).contains(&h2));
assert_ne!(h0, h1 - 64);
assert_ne!(h0, h2 - 128);
}
#[test]
fn test_get_fingerprint() {
let fp1 = BinaryFuseFilter::get_fingerprint(42, 8);
let fp2 = BinaryFuseFilter::get_fingerprint(43, 8);
let _ = fp1;
let _ = fp2;
}
}