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 => {
82                    if first_deleted.is_none() {
83                        first_deleted = Some(idx);
84                    }
85                }
86                _ => {}
87            }
88        }
89        first_deleted.unwrap_or(0)
90    }
91
92    fn maybe_resize(&mut self) {
93        let load = (self.count + self.deleted) as f32 / self.capacity as f32;
94        if load > LOAD_FACTOR_MAX {
95            let new_cap = self.capacity * 2;
96            let old_slots =
97                std::mem::replace(&mut self.slots, (0..new_cap).map(|_| Slot::Empty).collect());
98            self.capacity = new_cap;
99            self.count = 0;
100            self.deleted = 0;
101            for slot in old_slots {
102                if let Slot::Occupied(k, v) = slot {
103                    self.insert(k, v);
104                }
105            }
106        }
107    }
108
109    /// Insert or overwrite a key-value pair.
110    #[allow(dead_code)]
111    pub fn insert(&mut self, key: i64, value: i64) {
112        self.maybe_resize();
113        let idx = self.probe_insert(key);
114        match &self.slots[idx] {
115            Slot::Occupied(k, _) if *k == key => {
116                self.slots[idx] = Slot::Occupied(key, value);
117            }
118            Slot::Deleted => {
119                self.slots[idx] = Slot::Occupied(key, value);
120                self.deleted = self.deleted.saturating_sub(1);
121                self.count += 1;
122            }
123            _ => {
124                self.slots[idx] = Slot::Occupied(key, value);
125                self.count += 1;
126            }
127        }
128    }
129
130    /// Look up a key.
131    #[allow(dead_code)]
132    pub fn get(&self, key: i64) -> Option<i64> {
133        let idx = self.probe(key)?;
134        match &self.slots[idx] {
135            Slot::Occupied(_, v) => Some(*v),
136            _ => None,
137        }
138    }
139
140    /// Check if a key exists.
141    #[allow(dead_code)]
142    pub fn contains(&self, key: i64) -> bool {
143        self.probe(key).is_some()
144    }
145
146    /// Remove a key. Returns true if it existed.
147    #[allow(dead_code)]
148    pub fn remove(&mut self, key: i64) -> bool {
149        if let Some(idx) = self.probe(key) {
150            self.slots[idx] = Slot::Deleted;
151            self.count -= 1;
152            self.deleted += 1;
153            true
154        } else {
155            false
156        }
157    }
158
159    /// Number of live entries.
160    #[allow(dead_code)]
161    pub fn len(&self) -> usize {
162        self.count
163    }
164
165    /// Returns true if empty.
166    #[allow(dead_code)]
167    pub fn is_empty(&self) -> bool {
168        self.count == 0
169    }
170
171    /// Load factor (live + deleted / capacity).
172    #[allow(dead_code)]
173    pub fn load_factor(&self) -> f32 {
174        self.count as f32 / self.capacity as f32
175    }
176
177    /// Collect all (key, value) pairs.
178    #[allow(dead_code)]
179    pub fn pairs(&self) -> Vec<(i64, i64)> {
180        self.slots
181            .iter()
182            .filter_map(|s| {
183                if let Slot::Occupied(k, v) = s {
184                    Some((*k, *v))
185                } else {
186                    None
187                }
188            })
189            .collect()
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn insert_and_get() {
199        let mut m = OpenHashMap::new();
200        m.insert(42, 420);
201        assert_eq!(m.get(42), Some(420));
202    }
203
204    #[test]
205    fn get_missing_is_none() {
206        let m = OpenHashMap::new();
207        assert!(m.get(1).is_none());
208    }
209
210    #[test]
211    fn contains_after_insert() {
212        let mut m = OpenHashMap::new();
213        m.insert(7, 70);
214        assert!(m.contains(7));
215    }
216
217    #[test]
218    fn overwrite_existing() {
219        let mut m = OpenHashMap::new();
220        m.insert(1, 10);
221        m.insert(1, 20);
222        assert_eq!(m.get(1), Some(20));
223        assert_eq!(m.len(), 1);
224    }
225
226    #[test]
227    fn remove_existing() {
228        let mut m = OpenHashMap::new();
229        m.insert(3, 3);
230        assert!(m.remove(3));
231        assert!(!m.contains(3));
232    }
233
234    #[test]
235    fn remove_missing_returns_false() {
236        let mut m = OpenHashMap::new();
237        assert!(!m.remove(99));
238    }
239
240    #[test]
241    fn len_tracks_insertions() {
242        let mut m = OpenHashMap::new();
243        m.insert(1, 1);
244        m.insert(2, 2);
245        assert_eq!(m.len(), 2);
246    }
247
248    #[test]
249    fn is_empty_initially() {
250        let m = OpenHashMap::new();
251        assert!(m.is_empty());
252    }
253
254    #[test]
255    fn many_inserts_all_findable() {
256        let mut m = OpenHashMap::new();
257        for i in 0..50i64 {
258            m.insert(i, i * 2);
259        }
260        for i in 0..50i64 {
261            assert_eq!(m.get(i), Some(i * 2));
262        }
263    }
264
265    #[test]
266    fn pairs_count_matches_len() {
267        let mut m = OpenHashMap::new();
268        m.insert(10, 10);
269        m.insert(20, 20);
270        assert_eq!(m.pairs().len(), m.len());
271    }
272}