Skip to main content

oxihuman_core/
hash_bucket.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Hash-bucket map: a simple open-bucket hash table for u64 keys.
6
7/// Single bucket entry.
8#[derive(Debug, Clone)]
9#[allow(dead_code)]
10pub struct BucketEntry {
11    pub key: u64,
12    pub value: u64,
13}
14
15/// Hash-bucket map with configurable bucket count.
16#[derive(Debug)]
17#[allow(dead_code)]
18pub struct HashBucket {
19    buckets: Vec<Vec<BucketEntry>>,
20    count: usize,
21}
22
23/// Create a HashBucket with `n_buckets` buckets.
24#[allow(dead_code)]
25pub fn new_hash_bucket(n_buckets: usize) -> HashBucket {
26    let n = if n_buckets == 0 { 16 } else { n_buckets };
27    HashBucket {
28        buckets: vec![Vec::new(); n],
29        count: 0,
30    }
31}
32
33fn bucket_idx(hb: &HashBucket, key: u64) -> usize {
34    (key as usize) % hb.buckets.len()
35}
36
37/// Insert or update a key-value pair.
38#[allow(dead_code)]
39pub fn hb_insert(hb: &mut HashBucket, key: u64, value: u64) {
40    let idx = bucket_idx(hb, key);
41    for entry in &mut hb.buckets[idx] {
42        if entry.key == key {
43            entry.value = value;
44            return;
45        }
46    }
47    hb.buckets[idx].push(BucketEntry { key, value });
48    hb.count += 1;
49}
50
51/// Look up a key.
52#[allow(dead_code)]
53pub fn hb_get(hb: &HashBucket, key: u64) -> Option<u64> {
54    let idx = bucket_idx(hb, key);
55    hb.buckets[idx]
56        .iter()
57        .find(|e| e.key == key)
58        .map(|e| e.value)
59}
60
61/// Remove a key; returns old value if present.
62#[allow(dead_code)]
63pub fn hb_remove(hb: &mut HashBucket, key: u64) -> Option<u64> {
64    let idx = bucket_idx(hb, key);
65    let bucket = &mut hb.buckets[idx];
66    if let Some(pos) = bucket.iter().position(|e| e.key == key) {
67        let val = bucket.remove(pos).value;
68        hb.count -= 1;
69        Some(val)
70    } else {
71        None
72    }
73}
74
75/// Whether a key exists.
76#[allow(dead_code)]
77pub fn hb_contains(hb: &HashBucket, key: u64) -> bool {
78    hb_get(hb, key).is_some()
79}
80
81/// Total number of stored entries.
82#[allow(dead_code)]
83pub fn hb_count(hb: &HashBucket) -> usize {
84    hb.count
85}
86
87/// Number of buckets.
88#[allow(dead_code)]
89pub fn hb_bucket_count(hb: &HashBucket) -> usize {
90    hb.buckets.len()
91}
92
93/// Clear all entries.
94#[allow(dead_code)]
95pub fn hb_clear(hb: &mut HashBucket) {
96    for bucket in &mut hb.buckets {
97        bucket.clear();
98    }
99    hb.count = 0;
100}
101
102/// Collect all keys.
103#[allow(dead_code)]
104pub fn hb_keys(hb: &HashBucket) -> Vec<u64> {
105    hb.buckets
106        .iter()
107        .flat_map(|b| b.iter().map(|e| e.key))
108        .collect()
109}
110
111/// Load factor (entries / buckets).
112#[allow(dead_code)]
113pub fn hb_load_factor(hb: &HashBucket) -> f32 {
114    hb.count as f32 / hb.buckets.len() as f32
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn test_insert_get() {
123        let mut hb = new_hash_bucket(8);
124        hb_insert(&mut hb, 1, 100);
125        assert_eq!(hb_get(&hb, 1), Some(100));
126    }
127
128    #[test]
129    fn test_update() {
130        let mut hb = new_hash_bucket(8);
131        hb_insert(&mut hb, 5, 10);
132        hb_insert(&mut hb, 5, 20);
133        assert_eq!(hb_get(&hb, 5), Some(20));
134        assert_eq!(hb_count(&hb), 1);
135    }
136
137    #[test]
138    fn test_remove() {
139        let mut hb = new_hash_bucket(8);
140        hb_insert(&mut hb, 3, 300);
141        assert_eq!(hb_remove(&mut hb, 3), Some(300));
142        assert!(!hb_contains(&hb, 3));
143    }
144
145    #[test]
146    fn test_contains() {
147        let mut hb = new_hash_bucket(8);
148        hb_insert(&mut hb, 7, 77);
149        assert!(hb_contains(&hb, 7));
150        assert!(!hb_contains(&hb, 8));
151    }
152
153    #[test]
154    fn test_count() {
155        let mut hb = new_hash_bucket(4);
156        hb_insert(&mut hb, 1, 1);
157        hb_insert(&mut hb, 2, 2);
158        assert_eq!(hb_count(&hb), 2);
159    }
160
161    #[test]
162    fn test_clear() {
163        let mut hb = new_hash_bucket(8);
164        hb_insert(&mut hb, 1, 1);
165        hb_clear(&mut hb);
166        assert_eq!(hb_count(&hb), 0);
167    }
168
169    #[test]
170    fn test_keys() {
171        let mut hb = new_hash_bucket(8);
172        hb_insert(&mut hb, 10, 1);
173        hb_insert(&mut hb, 20, 2);
174        let mut keys = hb_keys(&hb);
175        keys.sort();
176        assert_eq!(keys, vec![10, 20]);
177    }
178
179    #[test]
180    fn test_collision_same_bucket() {
181        let mut hb = new_hash_bucket(1);
182        hb_insert(&mut hb, 1, 111);
183        hb_insert(&mut hb, 2, 222);
184        assert_eq!(hb_get(&hb, 1), Some(111));
185        assert_eq!(hb_get(&hb, 2), Some(222));
186    }
187
188    #[test]
189    fn test_load_factor() {
190        let mut hb = new_hash_bucket(10);
191        hb_insert(&mut hb, 1, 1);
192        hb_insert(&mut hb, 2, 2);
193        let lf = hb_load_factor(&hb);
194        assert!((lf - 0.2_f32).abs() < 1e-5);
195    }
196}