Skip to content

Commit 7702ff7

Browse files
committed
feat: add bloom filter learning artifacts
1 parent 3c3e547 commit 7702ff7

10 files changed

Lines changed: 239 additions & 0 deletions

Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,18 @@ path = "examples/soluciones/hashmap_collision_chain.rs"
133133
name = "solution_hashmap_cache_remove"
134134
path = "examples/soluciones/hashmap_cache_remove.rs"
135135

136+
[[example]]
137+
name = "solution_bloom_filter_cache_guard"
138+
path = "examples/soluciones/bloom_filter_cache_guard.rs"
139+
140+
[[example]]
141+
name = "solution_bloom_filter_size_estimate"
142+
path = "examples/soluciones/bloom_filter_size_estimate.rs"
143+
144+
[[example]]
145+
name = "solution_bloom_filter_false_positive_probe"
146+
path = "examples/soluciones/bloom_filter_false_positive_probe.rs"
147+
136148
[[bench]]
137149
name = "vector_bench"
138150
path = "benches/vector_bench.rs"
@@ -182,3 +194,8 @@ harness = false
182194
name = "hashmap_bench"
183195
path = "benches/hashmap_bench.rs"
184196
harness = false
197+
198+
[[bench]]
199+
name = "bloom_filter_bench"
200+
path = "benches/bloom_filter_bench.rs"
201+
harness = false

benches/bloom_filter_bench.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
use std::collections::HashSet;
3+
use std::hint::black_box;
4+
use std::time::{Duration, Instant};
5+
6+
const SIZE: usize = 20_000;
7+
8+
fn main() {
9+
println!("bloom filter benchmark (manual, std::time::Instant)");
10+
println!("size: {SIZE}");
11+
println!("insert: {:?}", bench_insert());
12+
println!("positive membership: {:?}", bench_positive_membership());
13+
println!("negative membership: {:?}", bench_negative_membership());
14+
println!("hashset exact membership: {:?}", bench_hashset_membership());
15+
}
16+
17+
fn bench_insert() -> Duration {
18+
let start = Instant::now();
19+
let mut filter = BloomFilter::with_estimated_items(SIZE, 0.01).unwrap();
20+
for key in 0..SIZE {
21+
filter.insert(&black_box(key));
22+
}
23+
black_box(filter.set_bit_count());
24+
start.elapsed()
25+
}
26+
27+
fn bench_positive_membership() -> Duration {
28+
let mut filter = BloomFilter::with_estimated_items(SIZE, 0.01).unwrap();
29+
for key in 0..SIZE {
30+
filter.insert(&key);
31+
}
32+
33+
let start = Instant::now();
34+
for key in 0..SIZE {
35+
black_box(filter.might_contain(&key));
36+
}
37+
start.elapsed()
38+
}
39+
40+
fn bench_negative_membership() -> Duration {
41+
let mut filter = BloomFilter::with_estimated_items(SIZE, 0.01).unwrap();
42+
for key in 0..SIZE {
43+
filter.insert(&key);
44+
}
45+
46+
let start = Instant::now();
47+
for key in SIZE..(SIZE * 2) {
48+
black_box(filter.might_contain(&key));
49+
}
50+
start.elapsed()
51+
}
52+
53+
fn bench_hashset_membership() -> Duration {
54+
let set: HashSet<usize> = (0..SIZE).collect();
55+
56+
let start = Instant::now();
57+
for key in 0..SIZE {
58+
black_box(set.contains(&key));
59+
}
60+
start.elapsed()
61+
}

diagrams/11-bloom-filter.mmd

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
flowchart TB
2+
title["Bloom Filter: pertenencia probabilistica"]
3+
4+
value["valor: url:/manual/rfc-0001"]
5+
h1["hash 1"]
6+
h2["hash 2"]
7+
h3["hash 3"]
8+
9+
value --> h1
10+
value --> h2
11+
value --> h3
12+
13+
subgraph bits["Arreglo de bits"]
14+
b0["0: 0"]
15+
b1["1: 1"]
16+
b2["2: 0"]
17+
b3["3: 1"]
18+
b4["4: 0"]
19+
b5["5: 1"]
20+
b6["6: 0"]
21+
end
22+
23+
h1 --> b1
24+
h2 --> b3
25+
h3 --> b5
26+
27+
insert["insert<br/>enciende k bits"]
28+
query["might_contain<br/>revisa k bits"]
29+
missing["algun bit en 0<br/>definitivamente no esta"]
30+
maybe["todos en 1<br/>podria estar"]
31+
fp["falso positivo<br/>colision de bits"]
32+
nofn["sin falsos negativos<br/>si se inserto, sus bits siguen en 1"]
33+
34+
value --> insert --> bits
35+
bits --> query
36+
query --> missing
37+
query --> maybe --> fp
38+
maybe --> nofn

examples/bloom_filter_advanced.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
3+
fn main() {
4+
let mut filter = BloomFilter::with_estimated_items(1_000, 0.02).unwrap();
5+
6+
for id in 0..1_000 {
7+
filter.insert(&format!("visited:{id}"));
8+
}
9+
10+
let false_positives = (10_000..11_000)
11+
.filter(|id| filter.might_contain(&format!("visited:{id}")))
12+
.count();
13+
let measured_rate = false_positives as f64 / 1_000.0;
14+
15+
println!("falsos positivos medidos: {false_positives}");
16+
println!("tasa medida: {measured_rate:.4}");
17+
println!(
18+
"tasa estimada por formula: {:.4}",
19+
filter.estimated_false_positive_rate()
20+
);
21+
}

examples/bloom_filter_basic.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
3+
fn main() {
4+
let mut filter = BloomFilter::new(128, 3).unwrap();
5+
6+
filter.insert(&"rust");
7+
filter.insert(&"data-structures");
8+
9+
println!("rust podria estar: {}", filter.might_contain(&"rust"));
10+
println!(
11+
"python definitivamente no esta: {}",
12+
!filter.might_contain(&"python")
13+
);
14+
println!("bits encendidos: {}", filter.set_bit_count());
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
3+
fn main() {
4+
let mut filter = BloomFilter::with_estimated_items(100, 0.01).unwrap();
5+
6+
for id in 0..40 {
7+
filter.insert(&format!("lesson:{id}"));
8+
}
9+
10+
println!("bits: {}", filter.bit_count());
11+
println!("hashes: {}", filter.hash_count());
12+
println!("inserciones: {}", filter.inserted_count());
13+
println!(
14+
"falso positivo estimado: {:.4}",
15+
filter.estimated_false_positive_rate()
16+
);
17+
}

examples/bloom_filter_real_case.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
3+
fn main() {
4+
let mut likely_cached = BloomFilter::with_estimated_items(500, 0.01).unwrap();
5+
6+
for key in [
7+
"GET /courses/rust-data-structures",
8+
"GET /courses/rust-algorithms",
9+
"GET /manual/rfc-0001",
10+
] {
11+
likely_cached.insert(&key);
12+
}
13+
14+
for key in [
15+
"GET /manual/rfc-0001",
16+
"GET /private/admin",
17+
"GET /courses/rust-data-structures",
18+
] {
19+
if likely_cached.might_contain(&key) {
20+
println!("consultar cache o base de datos: {key}");
21+
} else {
22+
println!("evitar lectura: {key} definitivamente no esta");
23+
}
24+
}
25+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
3+
fn main() {
4+
let mut guard = BloomFilter::with_estimated_items(100, 0.01).unwrap();
5+
6+
for key in ["user:1", "user:2", "course:rust-data-structures"] {
7+
guard.insert(&key);
8+
}
9+
10+
assert!(guard.might_contain(&"user:1"));
11+
assert!(!guard.might_contain(&"course:missing"));
12+
13+
println!("guardia de cache listo");
14+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
3+
fn main() {
4+
let mut filter = BloomFilter::with_estimated_items(500, 0.02).unwrap();
5+
6+
for id in 0..500 {
7+
filter.insert(&format!("url:{id}"));
8+
}
9+
10+
let false_positives = (5_000..5_500)
11+
.filter(|id| filter.might_contain(&format!("url:{id}")))
12+
.count();
13+
14+
assert!(false_positives < 40);
15+
println!("falsos positivos observados: {false_positives}");
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use rust_data_structures::bloom_filter::BloomFilter;
2+
3+
fn main() {
4+
let filter = BloomFilter::with_estimated_items(10_000, 0.01).unwrap();
5+
6+
assert!(filter.bit_count() >= 95_000);
7+
assert!(filter.hash_count() >= 6);
8+
assert_eq!(filter.inserted_count(), 0);
9+
10+
println!(
11+
"para 10000 elementos al 1%: {} bits y {} hashes",
12+
filter.bit_count(),
13+
filter.hash_count()
14+
);
15+
}

0 commit comments

Comments
 (0)