Skip to main content

cjc_data/detcoll/
sorted_vec_map.rs

1//! `SortedVecMap<K, V>` — small-to-medium sealed sorted map.
2//!
3//! Sorted `Vec<(K, V)>` with binary-search lookup. The "graduation" of
4//! `TinyDetMap` for sizes ~16–~10_000 where `BTreeMap`'s pointer-chasing
5//! cost outweighs `Vec`'s contiguous-memory wins.
6//!
7//! Iteration is sorted by `K`, so output is canonical. Iteration cost
8//! is `O(n)`; lookup is `O(log n)`. Insert/remove cost is `O(n)`
9//! (shift), so this is **not** the structure for hot mutation paths.
10//!
11//! Two construction modes:
12//! - `insert` one-by-one — `O(n²)` total, fine for sealed/build-once.
13//! - `from_sorted_unique` — `O(n)`, caller has already sorted.
14
15use std::cmp::Ordering;
16
17#[derive(Debug, Clone)]
18pub struct SortedVecMap<K: Ord, V> {
19    entries: Vec<(K, V)>,
20}
21
22impl<K: Ord, V> Default for SortedVecMap<K, V> {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl<K: Ord, V> SortedVecMap<K, V> {
29    pub fn new() -> Self {
30        Self {
31            entries: Vec::new(),
32        }
33    }
34
35    pub fn with_capacity(cap: usize) -> Self {
36        Self {
37            entries: Vec::with_capacity(cap),
38        }
39    }
40
41    /// Construct from a vector that the caller asserts is sorted by
42    /// `K` and contains no duplicates. Debug-build asserts both
43    /// invariants; release builds trust the caller.
44    pub fn from_sorted_unique(entries: Vec<(K, V)>) -> Self {
45        debug_assert!(
46            entries.windows(2).all(|w| w[0].0 < w[1].0),
47            "from_sorted_unique requires strictly sorted unique keys"
48        );
49        Self { entries }
50    }
51
52    /// Construct from any iterator. Sorts and dedups (last value wins).
53    pub fn from_iter_unsorted<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self
54    where
55        K: Clone,
56    {
57        let mut entries: Vec<(K, V)> = iter.into_iter().collect();
58        entries.sort_by(|a, b| a.0.cmp(&b.0));
59        // Dedup keeping the *last* occurrence per key, which matches
60        // `BTreeMap::insert` semantics.
61        let mut deduped: Vec<(K, V)> = Vec::with_capacity(entries.len());
62        for (k, v) in entries.into_iter() {
63            if let Some(last) = deduped.last_mut() {
64                if last.0 == k {
65                    last.1 = v;
66                    continue;
67                }
68            }
69            deduped.push((k, v));
70        }
71        Self { entries: deduped }
72    }
73
74    pub fn len(&self) -> usize {
75        self.entries.len()
76    }
77
78    pub fn is_empty(&self) -> bool {
79        self.entries.is_empty()
80    }
81
82    /// `O(log n)` lookup. Full key equality on hit (binary search
83    /// returns the exact match index).
84    pub fn get(&self, key: &K) -> Option<&V> {
85        self.entries
86            .binary_search_by(|(k, _)| k.cmp(key))
87            .ok()
88            .map(|i| &self.entries[i].1)
89    }
90
91    pub fn contains_key(&self, key: &K) -> bool {
92        self.entries
93            .binary_search_by(|(k, _)| k.cmp(key))
94            .is_ok()
95    }
96
97    /// Insert or update. `O(n)` worst case (shift). Returns previous
98    /// value on update.
99    pub fn insert(&mut self, key: K, value: V) -> Option<V> {
100        match self.entries.binary_search_by(|(k, _)| k.cmp(&key)) {
101            Ok(i) => Some(std::mem::replace(&mut self.entries[i].1, value)),
102            Err(i) => {
103                self.entries.insert(i, (key, value));
104                None
105            }
106        }
107    }
108
109    pub fn remove(&mut self, key: &K) -> Option<V> {
110        match self.entries.binary_search_by(|(k, _)| k.cmp(key)) {
111            Ok(i) => Some(self.entries.remove(i).1),
112            Err(_) => None,
113        }
114    }
115
116    /// Iterate `(&K, &V)` pairs sorted by `K`. Canonical order.
117    pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> + '_ {
118        self.entries.iter().map(|(k, v)| (k, v))
119    }
120
121    /// Range iteration. `[start, end)`.
122    pub fn range<Q>(&self, start: &Q, end: &Q) -> impl Iterator<Item = (&K, &V)> + '_
123    where
124        K: std::borrow::Borrow<Q>,
125        Q: Ord + ?Sized,
126    {
127        // Find lower and upper bounds via binary search.
128        let lo = self
129            .entries
130            .binary_search_by(|(k, _)| match k.borrow().cmp(start) {
131                Ordering::Less => Ordering::Less,
132                _ => Ordering::Greater,
133            })
134            .unwrap_or_else(|i| i);
135        let hi = self
136            .entries
137            .binary_search_by(|(k, _)| match k.borrow().cmp(end) {
138                Ordering::Less => Ordering::Less,
139                _ => Ordering::Greater,
140            })
141            .unwrap_or_else(|i| i);
142        self.entries[lo..hi].iter().map(|(k, v)| (k, v))
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn binary_search_lookup() {
152        let mut m = SortedVecMap::new();
153        for k in 0..100i32 {
154            m.insert(k, k * 10);
155        }
156        for k in 0..100 {
157            assert_eq!(m.get(&k), Some(&(k * 10)));
158        }
159        assert_eq!(m.get(&100), None);
160        assert_eq!(m.get(&-1), None);
161    }
162
163    #[test]
164    fn iter_is_sorted() {
165        let m = SortedVecMap::from_iter_unsorted(vec![
166            (3, "c"),
167            (1, "a"),
168            (4, "d"),
169            (2, "b"),
170        ]);
171        let keys: Vec<i32> = m.iter().map(|(k, _)| *k).collect();
172        assert_eq!(keys, vec![1, 2, 3, 4]);
173    }
174
175    #[test]
176    fn from_iter_unsorted_dedup_last_wins() {
177        let m = SortedVecMap::from_iter_unsorted(vec![(1, "a"), (1, "b"), (1, "c")]);
178        assert_eq!(m.get(&1), Some(&"c"));
179        assert_eq!(m.len(), 1);
180    }
181
182    #[test]
183    fn range_query() {
184        let m: SortedVecMap<i32, &str> = SortedVecMap::from_sorted_unique(vec![
185            (1, "a"),
186            (2, "b"),
187            (3, "c"),
188            (4, "d"),
189            (5, "e"),
190        ]);
191        let r: Vec<_> = m.range(&2, &5).map(|(k, _)| *k).collect();
192        assert_eq!(r, vec![2, 3, 4]);
193    }
194
195    #[test]
196    fn from_sorted_unique_debug_panics_on_unsorted() {
197        // Only check in debug. Skip this test in release.
198        if cfg!(debug_assertions) {
199            let result = std::panic::catch_unwind(|| {
200                SortedVecMap::from_sorted_unique(vec![(2, "b"), (1, "a")])
201            });
202            assert!(result.is_err());
203        }
204    }
205}