Skip to content

Commit 67d07a2

Browse files
authored
feat: implement hashmap and hashset support (#11)
1 parent 377965d commit 67d07a2

File tree

7 files changed

+80
-41
lines changed

7 files changed

+80
-41
lines changed

Diff for: README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ All credit for the algorithm goes to [N-R-K](https://github.com/N-R-K).
1818
1. **Direct Hashing**: One-shot hashing using `chibi_hash64()`
1919
2. **Simple Hasher**: Basic implementation using `ChibiHasher` (implements `std::hash::Hasher`)
2020
3. **Streaming Hasher**: Memory-efficient streaming with `StreamingChibiHasher` (implements `std::hash::Hasher`)
21+
4. **BuildHasher**: `ChibiHasher` implements `BuildHasher`. This allows using ChibiHash as the default hasher for `std::collections::HashMap` and `std::collections::HashSet`. Use `ChibiHashMap` and `ChibiHashSet` types.
2122
## Example
2223

2324
```rust
24-
use chibihash::{chibi_hash64, ChibiHasher, StreamingChibiHasher};
25+
use chibihash::{chibi_hash64, ChibiHasher, StreamingChibiHasher, ChibiHashMap, ChibiHashSet};
2526
use std::hash::Hasher;
2627

2728
fn main() {
@@ -39,6 +40,11 @@ fn main() {
3940
hasher.update(b"yellow ");
4041
hasher.update(b"world");
4142
println!("Streaming: {:016x}", hasher.finalize());
43+
44+
// Method 4: BuildHasher
45+
let mut set: ChibiHashSet<String> = ChibiHashSet::default();
46+
set.insert("hello".to_string());
47+
println!("BuildHasher: {}", set.contains("hello"));
4248
}
4349
```
4450

Diff for: benches/bench.rs

+8-11
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,9 @@ pub fn bench_streaming(c: &mut Criterion) {
5050
let input = vec![0u8; *size];
5151

5252
// Single-shot benchmark
53-
group.bench_with_input(
54-
BenchmarkId::new("single_shot", size),
55-
&input,
56-
|b, input| {
57-
b.iter(|| chibi_hash64(black_box(input), black_box(0)))
58-
}
59-
);
53+
group.bench_with_input(BenchmarkId::new("single_shot", size), &input, |b, input| {
54+
b.iter(|| chibi_hash64(black_box(input), black_box(0)))
55+
});
6056

6157
// Streaming benchmark - single update
6258
group.bench_with_input(
@@ -68,7 +64,7 @@ pub fn bench_streaming(c: &mut Criterion) {
6864
hasher.update(black_box(input));
6965
hasher.finalize()
7066
})
71-
}
67+
},
7268
);
7369

7470
// Streaming benchmark - split updates
@@ -85,7 +81,7 @@ pub fn bench_streaming(c: &mut Criterion) {
8581
hasher.update(black_box(second));
8682
hasher.finalize()
8783
})
88-
}
84+
},
8985
);
9086
}
9187
}
@@ -112,7 +108,7 @@ pub fn bench_streaming_small_chunks(c: &mut Criterion) {
112108
}
113109
hasher.finalize()
114110
})
115-
}
111+
},
116112
);
117113
}
118114

@@ -128,7 +124,8 @@ pub fn bench_streaming_realistic(c: &mut Criterion) {
128124
group.bench_function("file_chunks_32k", |b| {
129125
b.iter(|| {
130126
let mut hasher = StreamingChibiHasher::new(0);
131-
for chunk in large_input.chunks(32 * 1024) { // 32KB chunks
127+
for chunk in large_input.chunks(32 * 1024) {
128+
// 32KB chunks
132129
hasher.update(black_box(chunk));
133130
}
134131
hasher.finalize()

Diff for: benches/rust_vs_c.rs

+9-18
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extern "C" {
1212
#[cfg(feature = "ffi")]
1313
fn bench_cross_language(c: &mut Criterion) {
1414
let mut group = c.benchmark_group("rust_vs_c");
15-
15+
1616
// Test different input patterns
1717
let test_cases = vec![
1818
("zeros", vec![0u8; 1024]),
@@ -34,18 +34,13 @@ fn bench_cross_language(c: &mut Criterion) {
3434
for (pattern_name, pattern) in test_cases {
3535
for size in sizes.iter() {
3636
let input = &pattern[..*size];
37-
37+
3838
// Benchmark Rust implementation
3939
group.bench_with_input(
4040
BenchmarkId::new(format!("rust_{}", pattern_name), size),
4141
&input,
4242
|b, input| {
43-
b.iter(|| {
44-
black_box(chibihash::chibi_hash64(
45-
black_box(input),
46-
black_box(0)
47-
))
48-
})
43+
b.iter(|| black_box(chibihash::chibi_hash64(black_box(input), black_box(0))))
4944
},
5045
);
5146

@@ -58,7 +53,7 @@ fn bench_cross_language(c: &mut Criterion) {
5853
black_box(chibihash64(
5954
black_box(input.as_ptr() as *const c_void),
6055
black_box(input.len() as isize),
61-
black_box(0)
56+
black_box(0),
6257
))
6358
})
6459
},
@@ -75,17 +70,13 @@ fn bench_cross_language(c: &mut Criterion) {
7570
group.bench_with_input(
7671
BenchmarkId::new("rust_aligned", size),
7772
&aligned_data[..*size],
78-
|b, input| {
79-
b.iter(|| black_box(chibihash::chibi_hash64(black_box(input), 0)))
80-
},
73+
|b, input| b.iter(|| black_box(chibihash::chibi_hash64(black_box(input), 0))),
8174
);
8275

8376
group.bench_with_input(
8477
BenchmarkId::new("rust_unaligned", size),
8578
&unaligned_data[..*size],
86-
|b, input| {
87-
b.iter(|| black_box(chibihash::chibi_hash64(black_box(input), 0)))
88-
},
79+
|b, input| b.iter(|| black_box(chibihash::chibi_hash64(black_box(input), 0))),
8980
);
9081

9182
// Same for C implementation
@@ -97,7 +88,7 @@ fn bench_cross_language(c: &mut Criterion) {
9788
black_box(chibihash64(
9889
black_box(input.as_ptr() as *const c_void),
9990
black_box(input.len() as isize),
100-
0
91+
0,
10192
))
10293
})
10394
},
@@ -111,7 +102,7 @@ fn bench_cross_language(c: &mut Criterion) {
111102
black_box(chibihash64(
112103
black_box(input.as_ptr() as *const c_void),
113104
black_box(input.len() as isize),
114-
0
105+
0,
115106
))
116107
})
117108
},
@@ -132,4 +123,4 @@ criterion_main!(benches);
132123
fn main() {
133124
println!("This benchmark requires the 'ffi' feature to be enabled.");
134125
println!("Please run with: cargo bench --features ffi");
135-
}
126+
}

Diff for: build.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ fn main() {
99
println!("cargo:rerun-if-changed=csrc/chibihash.h");
1010
#[cfg(feature = "ffi")]
1111
println!("cargo:rerun-if-changed=csrc/chibihash.c");
12-
}
12+
}

Diff for: src/lib.rs

+36-5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//!
88
//! Basic usage:
99
//! ```rust
10-
//! use chibihash::{chibi_hash64, ChibiHasher, StreamingChibiHasher};
10+
//! use chibihash::{chibi_hash64, ChibiHasher, StreamingChibiHasher, ChibiHashMap, ChibiHashSet};
1111
//! use std::hash::Hasher;
1212
//!
1313
//! // Direct hashing
@@ -26,9 +26,25 @@
2626
//! hasher1.update(b"Hello, ");
2727
//! hasher1.update(b"World!");
2828
//! println!("{:016x}", hasher1.finalize());
29+
//!
30+
//! // Using BuildHasher as HashMap
31+
//! let mut map: ChibiHashMap<String, i32> = ChibiHashMap::default();
32+
//! map.insert("hello".to_string(), 42);
33+
//! println!("{:?}", map.get("hello"));
34+
//!
35+
//! // Using BuildHasher as HashSet
36+
//! let mut set: ChibiHashSet<String> = ChibiHashSet::default();
37+
//! set.insert("hello".to_string());
38+
//! println!("{}", set.contains("hello"));
39+
//!
40+
//! // Using BuildHasher as HashMap with custom seed
41+
//! let builder = ChibiHasher::new(42);
42+
//! let mut map: ChibiHashMap<String, i32> = ChibiHashMap::with_hasher(builder);
43+
//! map.insert("hello".to_string(), 42);
44+
//! println!("{:?}", map.get("hello"));
2945
//! ```
3046
31-
use std::hash::{Hash, Hasher};
47+
use std::hash::{BuildHasher, Hash, Hasher};
3248

3349
const P1: u64 = 0x2B7E151628AED2A5;
3450
const P2: u64 = 0x9E3793492EEDC3F7;
@@ -104,7 +120,7 @@ fn load_u64_le(bytes: &[u8]) -> u64 {
104120
}
105121

106122
/// Configuration for the hash function
107-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
123+
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
108124
pub struct ChibiHasher {
109125
seed: u64,
110126
buffer: Vec<u8>,
@@ -135,6 +151,20 @@ impl Hasher for ChibiHasher {
135151
}
136152
}
137153

154+
impl BuildHasher for ChibiHasher {
155+
type Hasher = ChibiHasher;
156+
157+
fn build_hasher(&self) -> Self::Hasher {
158+
ChibiHasher::new(self.seed)
159+
}
160+
}
161+
162+
/// A HashMap that uses ChibiHash by default
163+
pub type ChibiHashMap<K, V> = std::collections::HashMap<K, V, ChibiHasher>;
164+
165+
/// A HashSet that uses ChibiHash by default
166+
pub type ChibiHashSet<T> = std::collections::HashSet<T, ChibiHasher>;
167+
138168
/// Streaming ChibiHasher that processes data incrementally
139169
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
140170
pub struct StreamingChibiHasher {
@@ -221,7 +251,8 @@ impl StreamingChibiHasher {
221251
h[0] ^= h[0] >> 31;
222252

223253
let mut i = 1;
224-
while l >= 8 && i < 4 { // bounds check
254+
while l >= 8 && i < 4 {
255+
// bounds check
225256
h[i] ^= load_u64_le(p);
226257
h[i] = h[i].wrapping_mul(P2);
227258
h[i] ^= h[i] >> 31;
@@ -276,4 +307,4 @@ mod tests {
276307
let bytes = [1, 2, 3, 4, 5, 6, 7, 8];
277308
assert_eq!(load_u64_le(&bytes), 0x0807060504030201);
278309
}
279-
}
310+
}

Diff for: tests/hash_tests.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Tests for the ChibiHash algorithm
22
//! Direct hashing
33
4-
use chibihash::{chibi_hash64, ChibiHasher, StreamingChibiHasher};
4+
use chibihash::{chibi_hash64, ChibiHashMap, ChibiHashSet, ChibiHasher, StreamingChibiHasher};
55
use std::hash::{Hash, Hasher};
66

77
#[test]
@@ -48,11 +48,11 @@ fn test_streaming_matches_direct() {
4848
// Helper function to test streaming vs direct
4949
fn test_streaming(input: &[u8], seed: u64) {
5050
let direct = chibi_hash64(input, seed);
51-
51+
5252
let mut streaming = StreamingChibiHasher::new(seed);
5353
streaming.update(input);
5454
let streaming_result = streaming.finalize();
55-
55+
5656
assert_eq!(
5757
direct, streaming_result,
5858
"Streaming and direct hashing mismatch for input: {:?}, seed: {}",
@@ -80,3 +80,17 @@ fn test_streaming_matches_direct() {
8080
"Split streaming should match direct"
8181
);
8282
}
83+
84+
#[test]
85+
fn test_chibi_hash_map() {
86+
let mut map: ChibiHashMap<String, i32> = ChibiHashMap::default();
87+
map.insert("hello".to_string(), 42);
88+
assert_eq!(map.get("hello"), Some(&42));
89+
}
90+
91+
#[test]
92+
fn test_chibi_hash_set() {
93+
let mut set: ChibiHashSet<String> = ChibiHashSet::default();
94+
set.insert("hello".to_string());
95+
assert!(set.contains("hello"));
96+
}

Diff for: tests/streaming_tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ fn test_streaming_matches_direct() {
1010
// Helper function to test streaming vs direct
1111
fn test_streaming(input: &[u8], seed: u64) {
1212
let direct = chibi_hash64(input, seed);
13-
13+
1414
let mut streaming = StreamingChibiHasher::new(seed);
1515
streaming.update(input);
1616
let streaming_result = streaming.finalize();
17-
17+
1818
assert_eq!(
1919
direct, streaming_result,
2020
"Streaming and direct hashing mismatch for input: {:?}, seed: {}",

0 commit comments

Comments
 (0)