use subms_bloom_filter::BloomFilter;
fn main() {
base_crawler_dedup();
shard_merge_and_occupancy();
#[cfg(feature = "counting")]
counting_session_set();
#[cfg(feature = "scalable")]
scalable_growth();
#[cfg(feature = "partitioned")]
partitioned_variant();
}
fn base_crawler_dedup() {
println!("== base: crawler URL dedup ==");
let mut seen = BloomFilter::new(10_000);
let frontier = [
"https://a.example/",
"https://b.example/",
"https://a.example/", "https://c.example/",
"https://b.example/", ];
let (mut fetched, mut skipped) = (0usize, 0usize);
for url in frontier {
if seen.might_contain(url) {
println!(" skip {url}");
skipped += 1;
} else {
seen.add(url);
println!(" fetch {url}");
fetched += 1;
}
}
println!(" -> fetched {fetched}, skipped {skipped}");
for url in [
"https://a.example/",
"https://b.example/",
"https://c.example/",
] {
assert!(seen.might_contain(url), "no false negatives");
}
}
fn shard_merge_and_occupancy() {
println!(
"
== merge: per-shard filters unioned at the gateway =="
);
let capacity = 10_000;
let mut gateway = BloomFilter::new(capacity);
for shard in 0..4 {
let mut local = BloomFilter::new(capacity);
for i in 0..500 {
local.add(&format!("shard{shard}-sym{i}"));
}
gateway.union(&local).expect("shards share one geometry");
}
println!(" merged 4 shards x 500 symbols");
println!(
" approx distinct keys: {}",
gateway.approximate_element_count()
);
println!(
" occupancy fpp: {:.4}%",
gateway.estimated_fpp() * 100.0
);
let mismatched = BloomFilter::new(capacity * 2);
match gateway.union(&mismatched) {
Err(e) => println!(" refused mismatched shard: {e}"),
Ok(()) => unreachable!("geometry check must reject this"),
}
gateway.clear();
println!(
" after clear -> approx distinct keys: {}",
gateway.approximate_element_count()
);
}
#[cfg(feature = "counting")]
fn counting_session_set() {
use subms_bloom_filter::CountingBloomFilter;
println!("\n== counting: active sessions with logout ==");
let mut sessions = CountingBloomFilter::new(1_000);
for s in ["sess-alice", "sess-bob", "sess-carol"] {
sessions.add(s);
}
println!(" bob active? {}", sessions.might_contain("sess-bob"));
sessions.remove("sess-bob"); println!(" bob after logout? {}", sessions.might_contain("sess-bob"));
assert!(
sessions.might_contain("sess-alice"),
"other sessions untouched"
);
}
#[cfg(feature = "scalable")]
fn scalable_growth() {
use subms_bloom_filter::ScalableBloomFilter;
println!("\n== scalable: grows past its initial capacity ==");
let mut f = ScalableBloomFilter::new(64);
for i in 0..1_000 {
f.add(&format!("key-{i}"));
}
println!(
" added 1000 into a cap-64 filter -> {} layers",
f.layer_count()
);
assert!(f.layer_count() > 1, "it grew");
for i in 0..1_000 {
assert!(
f.might_contain(&format!("key-{i}")),
"no false negatives after growth"
);
}
}
#[cfg(feature = "partitioned")]
fn partitioned_variant() {
use subms_bloom_filter::PartitionedBloomFilter;
println!("\n== partitioned: one slice per hash ==");
let mut f = PartitionedBloomFilter::new(1_000);
for tag in ["red", "green", "blue"] {
f.add(tag);
}
println!(" {} bits across {} slices", f.bit_count(), f.k());
println!(" green present? {}", f.might_contain("green"));
assert!(f.might_contain("red") && !f.might_contain("magenta-unseen"));
}