Skip to main content

cljrs_value/collections/
hash_map.rs

1use crate::Value;
2use crate::collections::array_map::PersistentArrayMap;
3use rpds::{HashTrieMapSync, RedBlackTreeMapSync};
4
5/// An immutable hash map that preserves insertion order when iterated.
6///
7/// Lookups go through a `rpds::HashTrieMap` from key to insertion sequence
8/// number; the sequence number then indexes a `rpds::RedBlackTreeMap` that
9/// holds the actual `(key, value)` pairs in insertion order. Re-associating
10/// an existing key keeps its original position (matching `PersistentArrayMap`
11/// and how most ordered-map implementations behave), so iteration order is
12/// deterministic and matches the order keys were first written, rather than
13/// depending on hash-bucket layout (which — since `rpds` seeds its hasher
14/// randomly per instance — would otherwise vary from run to run).
15///
16/// Small maps (≤8 entries) are represented as `PersistentArrayMap` instead;
17/// the two types share the same `Value::Map` variant.  `PersistentHashMap` is
18/// used once the entry count exceeds the array-map threshold.
19#[derive(Debug, Clone)]
20pub struct PersistentHashMap {
21    index: HashTrieMapSync<Value, u64>,
22    order: RedBlackTreeMapSync<u64, (Value, Value)>,
23    next_seq: u64,
24}
25
26impl PersistentHashMap {
27    pub fn empty() -> Self {
28        Self {
29            index: HashTrieMapSync::new_sync(),
30            next_seq: 0,
31            order: RedBlackTreeMapSync::new_sync(),
32        }
33    }
34
35    /// Build from a raw `HashTrieMap`, in its (arbitrary) iteration order.
36    ///
37    /// Prefer `from_pairs`/`assoc` when the caller has a meaningful source
38    /// order to preserve.
39    pub fn new(map: HashTrieMapSync<Value, Value>) -> Self {
40        Self::from_pairs(map.iter().map(|(k, v)| (k.clone(), v.clone())))
41    }
42
43    pub fn count(&self) -> usize {
44        self.index.size()
45    }
46
47    pub fn is_empty(&self) -> bool {
48        self.index.is_empty()
49    }
50
51    /// Look up a key.
52    pub fn get(&self, key: &Value) -> Option<&Value> {
53        let seq = self.index.get(key)?;
54        self.order.get(seq).map(|(_, v)| v)
55    }
56
57    pub fn contains_key(&self, key: &Value) -> bool {
58        self.index.contains_key(key)
59    }
60
61    /// Return a new map with `key` → `value`.
62    ///
63    /// Re-associating a key that is already present keeps its original
64    /// insertion position; a brand-new key is appended at the end.
65    pub fn assoc(&self, key: Value, value: Value) -> Self {
66        if let Some(seq) = self.index.get(&key) {
67            let seq = *seq;
68            Self {
69                index: self.index.clone(),
70                order: self.order.insert(seq, (key, value)),
71                next_seq: self.next_seq,
72            }
73        } else {
74            let seq = self.next_seq;
75            Self {
76                index: self.index.insert(key.clone(), seq),
77                order: self.order.insert(seq, (key, value)),
78                next_seq: seq + 1,
79            }
80        }
81    }
82
83    /// Return a new map with `key` removed.
84    pub fn dissoc(&self, key: &Value) -> Self {
85        match self.index.get(key) {
86            Some(seq) => Self {
87                index: self.index.remove(key),
88                order: self.order.remove(seq),
89                next_seq: self.next_seq,
90            },
91            None => self.clone(),
92        }
93    }
94
95    /// Iterate over all `(key, value)` pairs in insertion order.
96    pub fn iter(&self) -> impl Iterator<Item = (&Value, &Value)> {
97        self.order.iter().map(|(_, (k, v))| (k, v))
98    }
99
100    /// Collect all keys, in insertion order.
101    pub fn keys(&self) -> Vec<Value> {
102        self.iter().map(|(k, _)| k.clone()).collect()
103    }
104
105    /// Collect all values, in insertion order.
106    pub fn vals(&self) -> Vec<Value> {
107        self.iter().map(|(_, v)| v.clone()).collect()
108    }
109
110    /// Merge two maps; right-hand side wins on key collision.
111    pub fn merge(&self, other: &Self) -> Self {
112        let mut result = self.clone();
113        for (k, v) in other.iter() {
114            result = result.assoc(k.clone(), v.clone());
115        }
116        result
117    }
118
119    /// Build from an iterator of `(key, value)` pairs, in the given order.
120    pub fn from_pairs<I: IntoIterator<Item = (Value, Value)>>(iter: I) -> Self {
121        let mut m = Self::empty();
122        for (k, v) in iter {
123            m = m.assoc(k, v);
124        }
125        m
126    }
127
128    /// Promote from a `PersistentArrayMap` when the threshold is exceeded.
129    pub fn from_array_map(am: &PersistentArrayMap) -> Self {
130        Self::from_pairs(am.iter().map(|(k, v)| (k.clone(), v.clone())))
131    }
132}
133
134impl PartialEq for PersistentHashMap {
135    fn eq(&self, other: &Self) -> bool {
136        if self.count() != other.count() {
137            return false;
138        }
139        self.iter().all(|(k, v)| other.get(k) == Some(v))
140    }
141}
142
143impl cljrs_gc::Trace for PersistentHashMap {
144    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
145        for (_, (k, v)) in self.order.iter() {
146            k.trace(visitor);
147            v.trace(visitor);
148        }
149    }
150
151    fn gc_size_extra(&self) -> usize {
152        // Per entry: index HashTrieMap entry (~40) + order RedBlackTree node
153        // (~48) + values stored inline in the order tree (not behind GcPtr).
154        let n = self.index.size();
155        n * (88 + 2 * std::mem::size_of::<Value>())
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use crate::Value;
163    use crate::collections::array_map::AssocResult;
164    use cljrs_gc::GcPtr;
165
166    fn kw(s: &str) -> Value {
167        Value::Keyword(GcPtr::new(crate::keyword::Keyword::simple(s)))
168    }
169    fn int(n: i64) -> Value {
170        Value::Long(n)
171    }
172
173    #[test]
174    fn test_basic_ops() {
175        let m = PersistentHashMap::empty();
176        let m = m.assoc(kw("a"), int(1));
177        let m = m.assoc(kw("b"), int(2));
178        assert_eq!(m.count(), 2);
179        assert_eq!(m.get(&kw("a")), Some(&int(1)));
180        assert_eq!(m.get(&kw("b")), Some(&int(2)));
181        assert_eq!(m.get(&kw("c")), None);
182    }
183
184    #[test]
185    fn test_update() {
186        let m = PersistentHashMap::empty()
187            .assoc(kw("a"), int(1))
188            .assoc(kw("a"), int(99));
189        assert_eq!(m.count(), 1);
190        assert_eq!(m.get(&kw("a")), Some(&int(99)));
191    }
192
193    #[test]
194    fn test_dissoc() {
195        let m = PersistentHashMap::empty()
196            .assoc(kw("a"), int(1))
197            .assoc(kw("b"), int(2));
198        let m2 = m.dissoc(&kw("a"));
199        assert_eq!(m2.count(), 1);
200        assert_eq!(m2.get(&kw("a")), None);
201        assert_eq!(m2.get(&kw("b")), Some(&int(2)));
202    }
203
204    #[test]
205    fn test_many_entries() {
206        let mut m = PersistentHashMap::empty();
207        for i in 0i64..200 {
208            m = m.assoc(int(i), int(i * 10));
209        }
210        assert_eq!(m.count(), 200);
211        for i in 0i64..200 {
212            assert_eq!(m.get(&int(i)), Some(&int(i * 10)));
213        }
214    }
215
216    #[test]
217    fn test_merge() {
218        let a = PersistentHashMap::empty()
219            .assoc(kw("a"), int(1))
220            .assoc(kw("b"), int(2));
221        let b = PersistentHashMap::empty()
222            .assoc(kw("b"), int(99))
223            .assoc(kw("c"), int(3));
224        let merged = a.merge(&b);
225        assert_eq!(merged.count(), 3);
226        assert_eq!(merged.get(&kw("a")), Some(&int(1)));
227        assert_eq!(merged.get(&kw("b")), Some(&int(99))); // right wins
228        assert_eq!(merged.get(&kw("c")), Some(&int(3)));
229    }
230
231    #[test]
232    fn test_equality() {
233        let a = PersistentHashMap::empty()
234            .assoc(kw("a"), int(1))
235            .assoc(kw("b"), int(2));
236        let b = PersistentHashMap::empty()
237            .assoc(kw("b"), int(2))
238            .assoc(kw("a"), int(1));
239        assert_eq!(a, b);
240    }
241
242    #[test]
243    fn test_from_array_map() {
244        let mut am = PersistentArrayMap::empty();
245        for i in 0..3i64 {
246            let AssocResult::Array(next) = am.assoc(int(i), int(i * 2)) else {
247                panic!()
248            };
249            am = next;
250        }
251        let hm = PersistentHashMap::from_array_map(&am);
252        assert_eq!(hm.count(), 3);
253        for i in 0..3i64 {
254            assert_eq!(hm.get(&int(i)), Some(&int(i * 2)));
255        }
256    }
257
258    #[test]
259    fn test_iteration_order_matches_insertion_order() {
260        let mut m = PersistentHashMap::empty();
261        let keys = ["j", "i", "h", "g", "f", "e", "d", "c", "b", "a", "z", "y"];
262        for k in keys {
263            m = m.assoc(kw(k), int(0));
264        }
265        let iterated: Vec<Value> = m.keys();
266        let expected: Vec<Value> = keys.iter().map(|k| kw(k)).collect();
267        assert_eq!(iterated, expected);
268    }
269
270    #[test]
271    fn test_reassoc_keeps_original_position() {
272        let m = PersistentHashMap::empty()
273            .assoc(kw("a"), int(1))
274            .assoc(kw("b"), int(2))
275            .assoc(kw("c"), int(3))
276            .assoc(kw("b"), int(99));
277        let keys = m.keys();
278        assert_eq!(keys, vec![kw("a"), kw("b"), kw("c")]);
279        assert_eq!(m.get(&kw("b")), Some(&int(99)));
280    }
281
282    #[test]
283    fn test_dissoc_preserves_remaining_order() {
284        let m = PersistentHashMap::empty()
285            .assoc(kw("a"), int(1))
286            .assoc(kw("b"), int(2))
287            .assoc(kw("c"), int(3))
288            .dissoc(&kw("b"));
289        assert_eq!(m.keys(), vec![kw("a"), kw("c")]);
290    }
291}