Skip to main content

small_collections/maps/
heapless_ordered_map.rs

1#![cfg(feature = "ordered")]
2//! Stack-allocated insertion-order map.
3//!
4//! This module is the **stack half** of [`SmallOrderedMap`](crate::SmallOrderedMap).
5//! It wraps `heapless::LinearMap` and adds full [`Borrow`]-based lookup and a `remove` that
6//! preserves insertion order, returning `Err((key, value))` when the inner map is full so
7//! the caller can transparently *spill* to a heap `OrderMap`.
8
9use std::borrow::Borrow;
10use std::hash::Hash;
11
12use heapless::LinearMap;
13
14// ─── HeaplessOrderedMap ───────────────────────────────────────────────────────
15
16/// A **stack-allocated**, insertion-order-preserving map backed by `heapless::LinearMap`.
17///
18/// # Architecture & Pseudocode
19/// This map stores elements linearly in the exact sequence they were added. It relies
20/// on a simple contiguous array layout under the hood, yielding O(N) operations.
21///
22/// - `map`: A `heapless::LinearMap<K, V, N>`.
23///
24/// ## Insert Algorithm
25/// ```text
26/// 1. If map is physically full (`len == N`) AND the key is not already inside:
27///    a. Return `Err((key, value))` (triggers heap spill in `SmallOrderedMap`).
28/// 2. Else (capacity available or updating existing key):
29///    a. Let `old_val = map.insert(key, value)`.
30///    b. Return `Ok(old_val)`.
31/// ```
32///
33/// ## Remove Algorithm (Order-Preserving)
34/// ```text
35/// 1. Initialize a temporary empty `LinearMap`.
36/// 2. Move the elements from the current map into `old_map` using `core::mem::replace`.
37/// 3. Iterate through `old_map` elements:
38///    a. If `element.key == target_key` (and we haven't removed it yet):
39///       i. Save `element.value` as the return value.
40///    b. Else:
41///       i. Insert `element` into the temporary map.
42/// 4. Replace `self.map` with the temporary map.
43/// 5. Return the saved `old_val`.
44/// ```
45#[derive(Debug, Clone)]
46pub struct HeaplessOrderedMap<K: Eq + Hash, V, const N: usize> {
47    map: LinearMap<K, V, N>,
48}
49
50impl<K: Eq + Hash, V, const N: usize> HeaplessOrderedMap<K, V, N> {
51    /// Automatically generated documentation for this item.
52    pub fn new() -> Self {
53        const {
54            assert!(
55                std::mem::size_of::<Self>() <= 16 * 1024,
56                "HeaplessOrderedMap is too large! The struct size exceeds the 16KB limit. Reduce N."
57            );
58        }
59        Self {
60            map: LinearMap::new(),
61        }
62    }
63
64    /// Returns the number of entries currently stored.
65    pub fn len(&self) -> usize {
66        self.map.len()
67    }
68
69    /// Returns `true` if the map contains no entries.
70    pub fn is_empty(&self) -> bool {
71        self.map.is_empty()
72    }
73
74    /// Returns `true` if the map has reached its compile-time capacity `N`.
75    ///
76    /// When `is_full()` returns `true`, inserting a *new* key will return `Err`.
77    pub fn is_full(&self) -> bool {
78        self.map.len() == N
79    }
80
81    /// Removes all entries, dropping keys and values in place.
82    pub fn clear(&mut self) {
83        self.map.clear();
84    }
85
86    /// Inserts or updates a key-value pair.
87    ///
88    /// # Returns
89    /// | Variant | Meaning |
90    /// |---------|---------|
91    /// | `Ok(Some(old))` | Key already existed; old value returned, new value stored in place. |
92    /// | `Ok(None)` | Key was new; entry appended (insertion order preserved). |
93    /// | `Err((key, value))` | Map is full and key is new; **caller must spill to heap**. |
94    ///
95    /// # Complexity
96    /// O(N) — linear scan to check for the existing key before delegating to `LinearMap`.
97    pub fn insert(&mut self, key: K, value: V) -> Result<Option<V>, (K, V)> {
98        if self.map.len() == N && !self.map.contains_key(&key) {
99            return Err((key, value));
100        }
101        Ok(self.map.insert(key, value).ok().flatten())
102    }
103
104    /// Returns a shared reference to the value associated with `key`, or `None`.
105    ///
106    /// Accepts any type `Q` where `K: Borrow<Q>` and `Q: Hash + Eq`.
107    /// Complexity: O(N) linear scan.
108    pub fn get<Q>(&self, key: &Q) -> Option<&V>
109    where
110        K: Borrow<Q>,
111        Q: Hash + Eq + ?Sized,
112    {
113        self.map
114            .iter()
115            .find(|&(k, _)| <K as Borrow<Q>>::borrow(k) == key)
116            .map(|(_, v)| v)
117    }
118
119    /// Returns an exclusive reference to the value associated with `key`, or `None`.
120    ///
121    /// Accepts any type `Q` where `K: Borrow<Q>` and `Q: Hash + Eq`.
122    /// Complexity: O(N) linear scan.
123    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
124    where
125        K: Borrow<Q>,
126        Q: Hash + Eq + ?Sized,
127    {
128        self.map
129            .iter_mut()
130            .find(|(k, _)| <K as Borrow<Q>>::borrow(k) == key)
131            .map(|(_, v)| v)
132    }
133
134    /// Removes and returns the value for `key`, preserving insertion order for remaining entries.
135    ///
136    /// Accepts any type `Q` where `K: Borrow<Q>` and `Q: Hash + Eq`.
137    ///
138    /// # Implementation note
139    /// `heapless::LinearMap::remove` requires `K: PartialEq<Q>`, which is more restrictive
140    /// than `K: Borrow<Q>`.  To stay generic we drain the map into a temporary buffer,
141    /// skipping the matching entry, then write the buffer back.  This preserves insertion
142    /// order and is allocation-free, at the cost of O(N) time.
143    pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
144    where
145        K: Borrow<Q>,
146        Q: Hash + Eq + ?Sized,
147    {
148        // LinearMap::remove requires K: PartialEq<Q>.
149        // We rebuild to stay generic over Borrow<Q>.
150        let mut temp: LinearMap<K, V, N> = LinearMap::new();
151        let mut removed = None;
152
153        let old = core::mem::replace(&mut self.map, LinearMap::new());
154        for (k, v) in old.into_iter() {
155            if k.borrow() == key && removed.is_none() {
156                removed = Some(v);
157            } else {
158                let _ = temp.insert(k, v);
159            }
160        }
161        self.map = temp;
162        removed
163    }
164
165    /// Returns `true` if the map contains an entry for `key`.
166    ///
167    /// Accepts any type `Q` where `K: Borrow<Q>` and `Q: Hash + Eq`.
168    /// Complexity: O(N).
169    pub fn contains_key<Q>(&self, key: &Q) -> bool
170    where
171        K: Borrow<Q>,
172        Q: Hash + Eq + ?Sized,
173    {
174        self.get(key).is_some()
175    }
176
177    /// Returns an iterator over `(&K, &V)` pairs in insertion order.
178    pub fn iter(&self) -> heapless::linear_map::Iter<'_, K, V> {
179        self.map.iter()
180    }
181
182    /// Consumes `self` and returns the underlying `heapless::LinearMap`.
183    ///
184    /// Useful when the caller needs direct access to the inner map, e.g. during spill-to-heap.
185    pub fn into_inner(self) -> LinearMap<K, V, N> {
186        self.map
187    }
188}
189
190impl<K, V, const N: usize> PartialEq for HeaplessOrderedMap<K, V, N>
191where
192    K: Eq + Hash,
193    V: PartialEq,
194{
195    fn eq(&self, other: &Self) -> bool {
196        if self.len() != other.len() {
197            return false;
198        }
199        self.iter().all(|(k, v)| other.get(k) == Some(v))
200    }
201}
202
203impl<K, V, const N: usize> Eq for HeaplessOrderedMap<K, V, N>
204where
205    K: Eq + Hash,
206    V: Eq,
207{
208}
209
210impl<K: Eq + Hash, V, const N: usize> Default for HeaplessOrderedMap<K, V, N> {
211    /// Creates an empty map.  Equivalent to [`HeaplessOrderedMap::new`].
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217impl<K: Eq + Hash, V, const N: usize> IntoIterator for HeaplessOrderedMap<K, V, N> {
218    type Item = (K, V);
219    type IntoIter = heapless::linear_map::IntoIter<K, V, N>;
220
221    /// Consumes `self` and returns an iterator over `(K, V)` pairs in insertion order.
222    fn into_iter(self) -> Self::IntoIter {
223        self.map.into_iter()
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_heapless_ordered_map_stack_ops_insertion_order() {
233        let mut map: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
234        map.insert(3, 30).unwrap();
235        map.insert(1, 10).unwrap();
236        map.insert(2, 20).unwrap();
237
238        let keys: Vec<_> = map.iter().map(|(k, _)| *k).collect();
239        assert_eq!(keys, vec![3, 1, 2]);
240    }
241
242    #[test]
243    fn test_heapless_ordered_map_stack_ops_update() {
244        let mut map: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
245        assert_eq!(map.insert(1, 10), Ok(None));
246        assert_eq!(map.insert(1, 20), Ok(Some(10)));
247        assert_eq!(map.get(&1), Some(&20));
248    }
249
250    #[test]
251    fn test_heapless_ordered_map_stack_ops_remove() {
252        let mut map: HeaplessOrderedMap<String, i32, 4> = HeaplessOrderedMap::new();
253        map.insert("a".into(), 1).unwrap();
254        map.insert("b".into(), 2).unwrap();
255        assert_eq!(map.remove("a"), Some(1));
256        assert_eq!(map.len(), 1);
257        assert_eq!(map.get("b"), Some(&2));
258    }
259
260    #[test]
261    fn test_heapless_ordered_map_stack_ops_full_returns_err() {
262        let mut map: HeaplessOrderedMap<i32, i32, 2> = HeaplessOrderedMap::new();
263        map.insert(1, 10).unwrap();
264        map.insert(2, 20).unwrap();
265        assert!(map.is_full());
266        assert_eq!(map.insert(3, 30), Err((3, 30)));
267    }
268
269    #[test]
270    fn test_heapless_ordered_map_stack_ops_get_mut() {
271        let mut map: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
272        map.insert(1, 10).unwrap();
273        if let Some(v) = map.get_mut(&1) {
274            *v = 42;
275        }
276        assert_eq!(map.get(&1), Some(&42));
277    }
278
279    #[test]
280    fn test_heapless_ordered_map_stack_ops_contains_key() {
281        let mut map: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
282        map.insert(1, 10).unwrap();
283        assert!(map.contains_key(&1));
284        assert!(!map.contains_key(&2));
285    }
286
287    #[test]
288    fn test_heapless_ordered_map_traits_clone_default() {
289        let map1: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::default();
290        let mut map2 = map1.clone();
291        map2.insert(7, 70).unwrap();
292        assert_eq!(map1.len(), 0);
293        assert_eq!(map2.len(), 1);
294    }
295
296    #[test]
297    fn test_heapless_ordered_map_stack_ops_borrow_lookup() {
298        let mut map: HeaplessOrderedMap<String, i32, 4> = HeaplessOrderedMap::new();
299        map.insert("Apple".to_string(), 100).unwrap();
300        assert_eq!(map.get("Apple"), Some(&100));
301        assert_eq!(map.get_mut("Apple"), Some(&mut 100));
302    }
303}
304
305#[cfg(test)]
306mod heapless_ordered_map_coverage_tests {
307    use super::*;
308
309    #[test]
310    fn test_is_empty_false() {
311        let mut map: HeaplessOrderedMap<i32, i32, 2> = HeaplessOrderedMap::new();
312        map.insert(1, 10).unwrap();
313        assert!(!map.is_empty());
314    }
315
316    #[test]
317    fn test_get_get_mut_missing() {
318        let mut map: HeaplessOrderedMap<i32, i32, 2> = HeaplessOrderedMap::new();
319        map.insert(1, 10).unwrap();
320
321        assert_eq!(map.get(&2), None);
322        assert_eq!(map.get_mut(&2), None);
323    }
324
325    #[test]
326    fn test_into_inner() {
327        let mut map: HeaplessOrderedMap<i32, i32, 2> = HeaplessOrderedMap::new();
328        map.insert(1, 10).unwrap();
329        let inner = map.into_inner();
330        assert_eq!(inner.len(), 1);
331        assert_eq!(inner.get(&1), Some(&10));
332    }
333
334    #[test]
335    fn test_partial_eq_variants() {
336        let mut m1: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
337        m1.insert(1, 10).unwrap();
338
339        let mut m2: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
340        m2.insert(1, 10).unwrap();
341
342        let mut m3: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
343        m3.insert(1, 10).unwrap();
344        m3.insert(2, 20).unwrap();
345
346        let mut m4: HeaplessOrderedMap<i32, i32, 4> = HeaplessOrderedMap::new();
347        m4.insert(1, 99).unwrap();
348
349        assert_eq!(m1, m2);
350        assert_ne!(m1, m3); // len diff
351        assert_ne!(m1, m4); // val diff
352    }
353}