use std::fs::File;
use std::io::Write;
use std::path::Path;
use crate::error::{Error, Result};
use crate::hash::murmur3_x64_128_h1;
const BLOOM_MAGIC: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0, b'v', b'0', b'2', b'\n'];
const K: u64 = 3;
const P: f64 = 0.01;
fn optimal_m(n: u64) -> u64 {
(-(n as f64) * P.ln() / (std::f64::consts::LN_2 * std::f64::consts::LN_2)).ceil() as u64
}
fn splitmix64(state: &mut u64) -> u64 {
*state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub struct KveiBuilder {
keys: [u64; 3],
m: u64,
n: u64,
bits: Vec<u64>,
empty: bool,
}
impl KveiBuilder {
pub fn new(key_count: u64) -> KveiBuilder {
if key_count < 2 {
return KveiBuilder {
keys: [0; 3],
m: 0,
n: key_count,
bits: Vec::new(),
empty: true,
};
}
let m = optimal_m(key_count).max(2);
let mut seed = 0x1234_5678_9ABC_DEF0u64 ^ key_count;
let keys = [
splitmix64(&mut seed),
splitmix64(&mut seed),
splitmix64(&mut seed),
];
let nwords = m.div_ceil(64) as usize;
KveiBuilder {
keys,
m,
n: key_count,
bits: vec![0u64; nwords],
empty: false,
}
}
pub fn add_hash(&mut self, mut hash: u64) {
if self.empty {
return;
}
for &key in &self.keys {
hash = hash.rotate_left(17) ^ key;
let i = hash % self.m;
self.bits[(i >> 6) as usize] |= 1u64 << (i & 63);
}
}
pub fn add_key(&mut self, key: &[u8], salt: u32) {
self.add_hash(murmur3_x64_128_h1(key, salt));
}
pub fn finish(self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
let mut f = File::create(path).map_err(|e| Error::io(path, e))?;
if self.empty {
return f.flush().map_err(|e| Error::io(path, e)); }
let mut out = Vec::with_capacity(60 + self.bits.len() * 8 + 48);
out.extend_from_slice(&BLOOM_MAGIC);
out.extend_from_slice(&K.to_le_bytes());
out.extend_from_slice(&self.n.to_le_bytes());
out.extend_from_slice(&self.m.to_le_bytes());
for k in self.keys {
out.extend_from_slice(&k.to_le_bytes());
}
for w in &self.bits {
out.extend_from_slice(&w.to_le_bytes());
}
out.extend_from_slice(&[0u8; 48]);
f.write_all(&out).map_err(|e| Error::io(path, e))?;
f.flush().map_err(|e| Error::io(path, e))
}
}
pub fn build_kvei(kv_path: impl AsRef<Path>, salt: u32, out_path: impl AsRef<Path>) -> Result<()> {
let seg = crate::seg::Seg::open(kv_path)?;
build_kvei_from_seg(&seg, salt, out_path)
}
pub fn build_kvei_from_seg(
seg: &crate::seg::Seg,
salt: u32,
out_path: impl AsRef<Path>,
) -> Result<()> {
let key_count = seg.words_count() / 2;
let mut b = KveiBuilder::new(key_count);
let mut g = seg.getter();
for _ in 0..key_count {
let key = g.next();
g.skip(); b.add_key(&key, salt);
}
b.finish(out_path)
}
#[cfg(test)]
mod tests {
use super::optimal_m;
#[test]
fn optimal_m_matches_real_files() {
assert_eq!(optimal_m(1_079_554), 10_347_589);
assert_eq!(optimal_m(338_868_457), 3_248_073_943);
}
}