use scirs2_core::random::rngs::StdRng;
use scirs2_core::random::{RngExt, SeedableRng};
use std::collections::hash_map::DefaultHasher;
use std::f64::consts::LN_2;
use std::hash::{Hash, Hasher};
pub struct BloomFilter {
bit_array: Vec<bool>,
size: usize,
hash_functions: usize,
inserted_count: usize,
}
impl BloomFilter {
pub fn new(capacity: usize, false_positive_rate: f64) -> Self {
let size = Self::optimal_size(capacity, false_positive_rate);
let hash_functions = Self::optimal_hash_functions(size, capacity);
Self {
bit_array: vec![false; size],
size,
hash_functions,
inserted_count: 0,
}
}
pub fn with_parameters(size: usize, hash_functions: usize) -> Self {
Self {
bit_array: vec![false; size],
size,
hash_functions,
inserted_count: 0,
}
}
fn optimal_size(capacity: usize, false_positive_rate: f64) -> usize {
let ln2_sq = LN_2 * LN_2;
(-(capacity as f64) * false_positive_rate.ln() / ln2_sq).ceil() as usize
}
fn optimal_hash_functions(size: usize, capacity: usize) -> usize {
((size as f64 / capacity as f64) * LN_2).ceil() as usize
}
fn hash_values<T: Hash>(&self, item: &T) -> Vec<usize> {
let mut hashes = Vec::with_capacity(self.hash_functions);
for i in 0..self.hash_functions {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
i.hash(&mut hasher);
hashes.push((hasher.finish() as usize) % self.size);
}
hashes
}
pub fn insert<T: Hash>(&mut self, item: &T) {
let hashes = self.hash_values(item);
for hash in hashes {
self.bit_array[hash] = true;
}
self.inserted_count += 1;
}
pub fn contains<T: Hash>(&self, item: &T) -> bool {
let hashes = self.hash_values(item);
hashes.iter().all(|&hash| self.bit_array[hash])
}
pub fn false_positive_probability(&self) -> f64 {
let bits_set = self.bit_array.iter().filter(|&&bit| bit).count() as f64;
let ratio = bits_set / self.size as f64;
ratio.powf(self.hash_functions as f64)
}
pub fn len(&self) -> usize {
self.inserted_count
}
pub fn is_empty(&self) -> bool {
self.inserted_count == 0
}
pub fn clear(&mut self) {
self.bit_array.fill(false);
self.inserted_count = 0;
}
pub fn stats(&self) -> BloomFilterStats {
let bits_set = self.bit_array.iter().filter(|&&bit| bit).count();
BloomFilterStats {
size: self.size,
hash_functions: self.hash_functions,
inserted_count: self.inserted_count,
bits_set,
load_factor: bits_set as f64 / self.size as f64,
false_positive_probability: self.false_positive_probability(),
}
}
}
#[derive(Debug, Clone)]
pub struct BloomFilterStats {
pub size: usize,
pub hash_functions: usize,
pub inserted_count: usize,
pub bits_set: usize,
pub load_factor: f64,
pub false_positive_probability: f64,
}
pub struct CountMinSketch {
counts: Vec<Vec<u32>>,
width: usize,
depth: usize,
total_count: u64,
}
impl CountMinSketch {
pub fn new(width: usize, depth: usize) -> Self {
Self {
counts: vec![vec![0; width]; depth],
width,
depth,
total_count: 0,
}
}
pub fn with_bounds(epsilon: f64, delta: f64) -> Self {
let width = (std::f64::consts::E / epsilon).ceil() as usize;
let depth = (1.0 / delta).ln().ceil() as usize;
Self::new(width, depth)
}
fn hash_values<T: Hash>(&self, item: &T) -> Vec<usize> {
let mut hashes = Vec::with_capacity(self.depth);
for i in 0..self.depth {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
i.hash(&mut hasher);
hashes.push((hasher.finish() as usize) % self.width);
}
hashes
}
pub fn add<T: Hash>(&mut self, item: &T, count: u32) {
let hashes = self.hash_values(item);
for (i, &hash) in hashes.iter().enumerate() {
self.counts[i][hash] = self.counts[i][hash].saturating_add(count);
}
self.total_count += count as u64;
}
pub fn increment<T: Hash>(&mut self, item: &T) {
self.add(item, 1);
}
pub fn estimate<T: Hash>(&self, item: &T) -> u32 {
let hashes = self.hash_values(item);
hashes
.iter()
.enumerate()
.map(|(i, &hash)| self.counts[i][hash])
.min()
.unwrap_or(0)
}
pub fn total_count(&self) -> u64 {
self.total_count
}
pub fn clear(&mut self) {
for row in &mut self.counts {
row.fill(0);
}
self.total_count = 0;
}
pub fn stats(&self) -> CountMinSketchStats {
let max_count = self
.counts
.iter()
.flat_map(|row| row.iter())
.max()
.copied()
.unwrap_or(0);
let avg_count = if self.width * self.depth > 0 {
self.total_count as f64 / (self.width * self.depth) as f64
} else {
0.0
};
CountMinSketchStats {
width: self.width,
depth: self.depth,
total_count: self.total_count,
max_count,
avg_count,
}
}
}
#[derive(Debug, Clone)]
pub struct CountMinSketchStats {
pub width: usize,
pub depth: usize,
pub total_count: u64,
pub max_count: u32,
pub avg_count: f64,
}
pub struct HyperLogLog {
buckets: Vec<u8>,
bucket_count: usize,
alpha: f64,
}
impl HyperLogLog {
pub fn new(precision: u8) -> Self {
assert!(
(4..=16).contains(&precision),
"Precision must be between 4 and 16"
);
let bucket_count = 1 << precision;
let alpha = Self::calculate_alpha(bucket_count);
Self {
buckets: vec![0; bucket_count],
bucket_count,
alpha,
}
}
fn calculate_alpha(bucket_count: usize) -> f64 {
match bucket_count {
16 => 0.673,
32 => 0.697,
64 => 0.709,
_ => 0.7213 / (1.0 + 1.079 / bucket_count as f64),
}
}
fn hash_value<T: Hash>(&self, item: &T) -> u64 {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
hasher.finish()
}
fn leading_zeros(mut value: u64) -> u8 {
if value == 0 {
return 64;
}
let mut count = 0;
while (value & 0x8000000000000000) == 0 {
count += 1;
value <<= 1;
}
count
}
pub fn add<T: Hash>(&mut self, item: &T) {
let hash = self.hash_value(item);
let bucket_bits = 64 - (self.bucket_count as f64).log2() as u8;
let bucket = (hash >> bucket_bits) as usize;
let leading_zeros = Self::leading_zeros(hash << (64 - bucket_bits)) + 1;
if leading_zeros > self.buckets[bucket] {
self.buckets[bucket] = leading_zeros;
}
}
pub fn cardinality(&self) -> f64 {
let sum: f64 = self
.buckets
.iter()
.map(|&bucket| 2.0_f64.powf(-(bucket as f64)))
.sum();
let raw_estimate = self.alpha * (self.bucket_count as f64).powi(2) / sum;
if raw_estimate <= 2.5 * self.bucket_count as f64 {
let zero_buckets = self.buckets.iter().filter(|&&bucket| bucket == 0).count();
if zero_buckets != 0 {
return (self.bucket_count as f64)
* (self.bucket_count as f64 / zero_buckets as f64).ln();
}
} else if raw_estimate <= (1.0 / 30.0) * (1u64 << 32) as f64 {
return raw_estimate;
}
-((1u64 << 32) as f64) * (1.0 - raw_estimate / ((1u64 << 32) as f64)).ln()
}
pub fn merge(&mut self, other: &HyperLogLog) {
assert_eq!(
self.bucket_count, other.bucket_count,
"Cannot merge HyperLogLogs with different precisions"
);
for i in 0..self.bucket_count {
self.buckets[i] = self.buckets[i].max(other.buckets[i]);
}
}
pub fn clear(&mut self) {
self.buckets.fill(0);
}
pub fn stats(&self) -> HyperLogLogStats {
let max_bucket = *self.buckets.iter().max().unwrap_or(&0);
let zero_buckets = self.buckets.iter().filter(|&&bucket| bucket == 0).count();
let avg_bucket =
self.buckets.iter().map(|&b| b as f64).sum::<f64>() / self.bucket_count as f64;
HyperLogLogStats {
bucket_count: self.bucket_count,
cardinality: self.cardinality(),
max_bucket,
zero_buckets,
avg_bucket,
}
}
}
#[derive(Debug, Clone)]
pub struct HyperLogLogStats {
pub bucket_count: usize,
pub cardinality: f64,
pub max_bucket: u8,
pub zero_buckets: usize,
pub avg_bucket: f64,
}
pub struct MinHash {
hashes: Vec<u64>,
hash_functions: usize,
}
impl MinHash {
pub fn new(hash_functions: usize) -> Self {
Self {
hashes: vec![u64::MAX; hash_functions],
hash_functions,
}
}
fn hash_values<T: Hash>(&self, item: &T) -> Vec<u64> {
let mut hashes = Vec::with_capacity(self.hash_functions);
for i in 0..self.hash_functions {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
i.hash(&mut hasher);
hashes.push(hasher.finish());
}
hashes
}
pub fn add<T: Hash>(&mut self, item: &T) {
let item_hashes = self.hash_values(item);
for (i, &hash) in item_hashes.iter().enumerate() {
if hash < self.hashes[i] {
self.hashes[i] = hash;
}
}
}
pub fn jaccard_similarity(&self, other: &MinHash) -> f64 {
assert_eq!(
self.hash_functions, other.hash_functions,
"MinHash objects must have the same number of hash functions"
);
let matches = self
.hashes
.iter()
.zip(other.hashes.iter())
.filter(|(&a, &b)| a == b)
.count();
matches as f64 / self.hash_functions as f64
}
pub fn clear(&mut self) {
self.hashes.fill(u64::MAX);
}
pub fn stats(&self) -> MinHashStats {
let initialized_hashes = self.hashes.iter().filter(|&&h| h != u64::MAX).count();
MinHashStats {
hash_functions: self.hash_functions,
initialized_hashes,
completion_ratio: initialized_hashes as f64 / self.hash_functions as f64,
}
}
}
#[derive(Debug, Clone)]
pub struct MinHashStats {
pub hash_functions: usize,
pub initialized_hashes: usize,
pub completion_ratio: f64,
}
pub struct LSHash {
hash_tables: Vec<Vec<Vec<usize>>>,
projections: Vec<Vec<f64>>,
table_count: usize,
dimension: usize,
bucket_width: f64,
}
impl LSHash {
pub fn new(dimension: usize, table_count: usize, bucket_width: f64) -> Self {
let mut projections = Vec::with_capacity(table_count);
let mut rng = StdRng::seed_from_u64(42);
for _ in 0..table_count {
let mut projection = Vec::with_capacity(dimension);
for _ in 0..dimension {
projection.push(rng.random::<f64>() * 2.0 - 1.0); }
projections.push(projection);
}
Self {
hash_tables: vec![Vec::new(); table_count],
projections,
table_count,
dimension,
bucket_width,
}
}
fn hash_vector(&self, vector: &[f64], table_idx: usize) -> i32 {
let dot_product: f64 = vector
.iter()
.zip(self.projections[table_idx].iter())
.map(|(&v, &p)| v * p)
.sum();
(dot_product / self.bucket_width).floor() as i32
}
pub fn add(&mut self, vector: &[f64], data_idx: usize) {
assert_eq!(
vector.len(),
self.dimension,
"Vector dimension must match LSH dimension"
);
for table_idx in 0..self.table_count {
let hash = self.hash_vector(vector, table_idx);
if hash >= 0 {
let bucket_idx = hash as usize;
if self.hash_tables[table_idx].len() <= bucket_idx {
self.hash_tables[table_idx].resize(bucket_idx + 1, Vec::new());
}
self.hash_tables[table_idx][bucket_idx].push(data_idx);
}
}
}
pub fn query(&self, vector: &[f64]) -> Vec<usize> {
assert_eq!(
vector.len(),
self.dimension,
"Vector dimension must match LSH dimension"
);
let mut candidates = std::collections::HashSet::new();
for table_idx in 0..self.table_count {
let hash = self.hash_vector(vector, table_idx);
if hash >= 0 && (hash as usize) < self.hash_tables[table_idx].len() {
for &candidate in &self.hash_tables[table_idx][hash as usize] {
candidates.insert(candidate);
}
}
}
candidates.into_iter().collect()
}
pub fn clear(&mut self) {
for table in &mut self.hash_tables {
table.clear();
}
}
pub fn stats(&self) -> LSHashStats {
let total_entries: usize = self
.hash_tables
.iter()
.flat_map(|table| table.iter())
.map(|bucket| bucket.len())
.sum();
let non_empty_buckets: usize = self
.hash_tables
.iter()
.flat_map(|table| table.iter())
.filter(|bucket| !bucket.is_empty())
.count();
let total_buckets: usize = self.hash_tables.iter().map(|table| table.len()).sum();
LSHashStats {
table_count: self.table_count,
dimension: self.dimension,
bucket_width: self.bucket_width,
total_entries,
total_buckets,
non_empty_buckets,
load_factor: if total_buckets > 0 {
non_empty_buckets as f64 / total_buckets as f64
} else {
0.0
},
}
}
}
#[derive(Debug, Clone)]
pub struct LSHashStats {
pub table_count: usize,
pub dimension: usize,
pub bucket_width: f64,
pub total_entries: usize,
pub total_buckets: usize,
pub non_empty_buckets: usize,
pub load_factor: f64,
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn test_bloom_filter() {
let mut filter = BloomFilter::new(1000, 0.01);
filter.insert(&"hello");
filter.insert(&"world");
filter.insert(&42);
assert!(filter.contains(&"hello"));
assert!(filter.contains(&"world"));
assert!(filter.contains(&42));
assert!(!filter.contains(&"not_inserted"));
assert_eq!(filter.len(), 3);
let stats = filter.stats();
assert!(stats.false_positive_probability < 0.1);
}
#[test]
fn test_count_min_sketch() {
let mut sketch = CountMinSketch::new(100, 5);
sketch.increment(&"apple");
sketch.increment(&"apple");
sketch.add(&"banana", 3);
sketch.increment(&"cherry");
assert!(sketch.estimate(&"apple") >= 2);
assert!(sketch.estimate(&"banana") >= 3);
assert!(sketch.estimate(&"cherry") >= 1);
assert_eq!(sketch.estimate(&"not_added"), 0);
assert_eq!(sketch.total_count(), 6);
}
#[test]
fn test_hyperloglog() {
let mut hll = HyperLogLog::new(8);
for i in 0..1000 {
hll.add(&i);
}
let cardinality = hll.cardinality();
assert!(cardinality > 800.0 && cardinality < 1200.0);
let mut hll2 = HyperLogLog::new(8);
for i in 500..1500 {
hll2.add(&i);
}
hll.merge(&hll2);
let merged_cardinality = hll.cardinality();
assert!(merged_cardinality > cardinality);
}
#[test]
fn test_minhash() {
let mut mh1 = MinHash::new(128);
let mut mh2 = MinHash::new(128);
let set1: HashSet<i32> = (0..100).collect();
let set2: HashSet<i32> = (50..150).collect();
for item in &set1 {
mh1.add(item);
}
for item in &set2 {
mh2.add(item);
}
let similarity = mh1.jaccard_similarity(&mh2);
assert!(similarity > 0.2 && similarity < 0.5);
}
#[test]
fn test_lsh() {
let mut lsh = LSHash::new(3, 5, 1.0);
lsh.add(&[1.0, 2.0, 3.0], 0);
lsh.add(&[1.1, 2.1, 3.1], 1);
lsh.add(&[5.0, 6.0, 7.0], 2);
let candidates = lsh.query(&[1.05, 2.05, 3.05]);
println!("LSH candidates: {:?}", candidates);
let stats = lsh.stats();
assert!(stats.table_count == 5);
assert!(stats.dimension == 3);
}
}