Skip to main content

oxihuman_core/
hash_map_open.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Open-addressing hash map with linear probing.
6
7const DEFAULT_CAPACITY: usize = 16;
8const LOAD_FACTOR_MAX: f32 = 0.7;
9
10#[derive(Debug, Clone)]
11enum Slot<K, V> {
12    Empty,
13    Deleted,
14    Occupied(K, V),
15}
16
17fn hash_key(key: i64, cap: usize) -> usize {
18    let mut h = key as u64;
19    h ^= h >> 33;
20    h = h.wrapping_mul(0xff51afd7ed558ccd);
21    h ^= h >> 33;
22    h as usize % cap
23}
24
25/// An open-addressing hash map with linear probing (i64 → i64).
26#[derive(Debug, Clone)]
27#[allow(dead_code)]
28pub struct OpenHashMap {
29    slots: Vec<Slot<i64, i64>>,
30    count: usize,
31    deleted: usize,
32    capacity: usize,
33}
34
35impl Default for OpenHashMap {
36    fn default() -> Self {
37        Self::with_capacity(DEFAULT_CAPACITY)
38    }
39}
40
41impl OpenHashMap {
42    /// Create a new hash map with default capacity.
43    #[allow(dead_code)]
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Create with a specific initial capacity.
49    #[allow(dead_code)]
50    pub fn with_capacity(cap: usize) -> Self {
51        let cap = cap.max(8);
52        Self {
53            slots: (0..cap).map(|_| Slot::Empty).collect(),
54            count: 0,
55            deleted: 0,
56            capacity: cap,
57        }
58    }
59
60    fn probe(&self, key: i64) -> Option<usize> {
61        let start = hash_key(key, self.capacity);
62        for i in 0..self.capacity {
63            let idx = (start + i) % self.capacity;
64            match &self.slots[idx] {
65                Slot::Occupied(k, _) if *k == key => return Some(idx),
66                Slot::Empty => return None,
67                _ => {}
68            }
69        }
70        None
71    }
72
73    fn probe_insert(&self, key: i64) -> usize {
74        let start = hash_key(key, self.capacity);
75        let mut first_deleted: Option<usize> = None;
76        for i in 0..self.capacity {
77            let idx = (start + i) % self.capacity;
78            match &self.slots[idx] {
79                Slot::Occupied(k, _) if *k == key => return idx,
80                Slot::Empty => return first_deleted.unwrap_or(idx),
81                Slot::Deleted if first_deleted.is_none() => {
82                    first_deleted = Some(idx);
83                }
84                _ => {}
85            }
86        }
87        first_deleted.unwrap_or(0)
88    }
89
90    fn maybe_resize(&mut self) {
91        let load = (self.count + self.deleted) as f32 / self.capacity as f32;
92        if load > LOAD_FACTOR_MAX {
93            let new_cap = self.capacity * 2;
94            let old_slots =
95                std::mem::replace(&mut self.slots, (0..new_cap).map(|_| Slot::Empty).collect());
96            self.capacity = new_cap;
97            self.count = 0;
98            self.deleted = 0;
99            for slot in old_slots {
100                if let Slot::Occupied(k, v) = slot {
101                    self.insert(k, v);
102                }
103            }
104        }
105    }
106
107    /// Insert or overwrite a key-value pair.
108    #[allow(dead_code)]
109    pub fn insert(&mut self, key: i64, value: i64) {
110        self.maybe_resize();
111        let idx = self.probe_insert(key);
112        match &self.slots[idx] {
113            Slot::Occupied(k, _) if *k == key => {
114                self.slots[idx] = Slot::Occupied(key, value);
115            }
116            Slot::Deleted => {
117                self.slots[idx] = Slot::Occupied(key, value);
118                self.deleted = self.deleted.saturating_sub(1);
119                self.count += 1;
120            }
121            _ => {
122                self.slots[idx] = Slot::Occupied(key, value);
123                self.count += 1;
124            }
125        }
126    }
127
128    /// Look up a key.
129    #[allow(dead_code)]
130    pub fn get(&self, key: i64) -> Option<i64> {
131        let idx = self.probe(key)?;
132        match &self.slots[idx] {
133            Slot::Occupied(_, v) => Some(*v),
134            _ => None,
135        }
136    }
137
138    /// Check if a key exists.
139    #[allow(dead_code)]
140    pub fn contains(&self, key: i64) -> bool {
141        self.probe(key).is_some()
142    }
143
144    /// Remove a key. Returns true if it existed.
145    #[allow(dead_code)]
146    pub fn remove(&mut self, key: i64) -> bool {
147        if let Some(idx) = self.probe(key) {
148            self.slots[idx] = Slot::Deleted;
149            self.count -= 1;
150            self.deleted += 1;
151            true
152        } else {
153            false
154        }
155    }
156
157    /// Number of live entries.
158    #[allow(dead_code)]
159    pub fn len(&self) -> usize {
160        self.count
161    }
162
163    /// Returns true if empty.
164    #[allow(dead_code)]
165    pub fn is_empty(&self) -> bool {
166        self.count == 0
167    }
168
169    /// Load factor (live + deleted / capacity).
170    #[allow(dead_code)]
171    pub fn load_factor(&self) -> f32 {
172        self.count as f32 / self.capacity as f32
173    }
174
175    /// Collect all (key, value) pairs.
176    #[allow(dead_code)]
177    pub fn pairs(&self) -> Vec<(i64, i64)> {
178        self.slots
179            .iter()
180            .filter_map(|s| {
181                if let Slot::Occupied(k, v) = s {
182                    Some((*k, *v))
183                } else {
184                    None
185                }
186            })
187            .collect()
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use super::*;
194
195    #[test]
196    fn insert_and_get() {
197        let mut m = OpenHashMap::new();
198        m.insert(42, 420);
199        assert_eq!(m.get(42), Some(420));
200    }
201
202    #[test]
203    fn get_missing_is_none() {
204        let m = OpenHashMap::new();
205        assert!(m.get(1).is_none());
206    }
207
208    #[test]
209    fn contains_after_insert() {
210        let mut m = OpenHashMap::new();
211        m.insert(7, 70);
212        assert!(m.contains(7));
213    }
214
215    #[test]
216    fn overwrite_existing() {
217        let mut m = OpenHashMap::new();
218        m.insert(1, 10);
219        m.insert(1, 20);
220        assert_eq!(m.get(1), Some(20));
221        assert_eq!(m.len(), 1);
222    }
223
224    #[test]
225    fn remove_existing() {
226        let mut m = OpenHashMap::new();
227        m.insert(3, 3);
228        assert!(m.remove(3));
229        assert!(!m.contains(3));
230    }
231
232    #[test]
233    fn remove_missing_returns_false() {
234        let mut m = OpenHashMap::new();
235        assert!(!m.remove(99));
236    }
237
238    #[test]
239    fn len_tracks_insertions() {
240        let mut m = OpenHashMap::new();
241        m.insert(1, 1);
242        m.insert(2, 2);
243        assert_eq!(m.len(), 2);
244    }
245
246    #[test]
247    fn is_empty_initially() {
248        let m = OpenHashMap::new();
249        assert!(m.is_empty());
250    }
251
252    #[test]
253    fn many_inserts_all_findable() {
254        let mut m = OpenHashMap::new();
255        for i in 0..50i64 {
256            m.insert(i, i * 2);
257        }
258        for i in 0..50i64 {
259            assert_eq!(m.get(i), Some(i * 2));
260        }
261    }
262
263    #[test]
264    fn pairs_count_matches_len() {
265        let mut m = OpenHashMap::new();
266        m.insert(10, 10);
267        m.insert(20, 20);
268        assert_eq!(m.pairs().len(), m.len());
269    }
270}