Skip to main content

cljrs_value/collections/
hash_map.rs

1use crate::Value;
2use crate::collections::array_map::PersistentArrayMap;
3use rpds::HashTrieMapSync;
4
5/// An immutable hash map backed by `rpds::HashTrieMap`.
6///
7/// Small maps (≤8 entries) are represented as `PersistentArrayMap` instead;
8/// the two types share the same `Value::Map` variant.  `PersistentHashMap` is
9/// used once the entry count exceeds the array-map threshold.
10#[derive(Debug, Clone)]
11pub struct PersistentHashMap {
12    inner: rpds::HashTrieMapSync<Value, Value>,
13}
14
15impl PersistentHashMap {
16    pub fn empty() -> Self {
17        Self {
18            inner: rpds::HashTrieMapSync::new_sync(),
19        }
20    }
21
22    pub fn new(map: HashTrieMapSync<Value, Value>) -> Self {
23        Self { inner: map }
24    }
25
26    pub fn count(&self) -> usize {
27        self.inner.size()
28    }
29
30    pub fn is_empty(&self) -> bool {
31        self.inner.is_empty()
32    }
33
34    /// Look up a key.
35    pub fn get(&self, key: &Value) -> Option<&Value> {
36        self.inner.get(key)
37    }
38
39    pub fn contains_key(&self, key: &Value) -> bool {
40        self.inner.contains_key(key)
41    }
42
43    /// Return a new map with `key` → `value`.
44    pub fn assoc(&self, key: Value, value: Value) -> Self {
45        Self {
46            inner: self.inner.insert(key, value),
47        }
48    }
49
50    /// Return a new map with `key` removed.
51    pub fn dissoc(&self, key: &Value) -> Self {
52        Self {
53            inner: self.inner.remove(key),
54        }
55    }
56
57    /// Iterate over all `(key, value)` pairs in an unspecified order.
58    pub fn iter(&self) -> impl Iterator<Item = (&Value, &Value)> {
59        self.inner.iter()
60    }
61
62    /// Collect all keys.
63    pub fn keys(&self) -> Vec<Value> {
64        self.inner.keys().cloned().collect()
65    }
66
67    /// Collect all values.
68    pub fn vals(&self) -> Vec<Value> {
69        self.inner.values().cloned().collect()
70    }
71
72    /// Merge two maps; right-hand side wins on key collision.
73    pub fn merge(&self, other: &Self) -> Self {
74        let mut result = self.clone();
75        for (k, v) in other.inner.iter() {
76            result = result.assoc(k.clone(), v.clone());
77        }
78        result
79    }
80
81    /// Build from an iterator of `(key, value)` pairs.
82    pub fn from_pairs<I: IntoIterator<Item = (Value, Value)>>(iter: I) -> Self {
83        let mut m = Self::empty();
84        for (k, v) in iter {
85            m = m.assoc(k, v);
86        }
87        m
88    }
89
90    /// Promote from a `PersistentArrayMap` when the threshold is exceeded.
91    pub fn from_array_map(am: &PersistentArrayMap) -> Self {
92        Self::from_pairs(am.iter().map(|(k, v)| (k.clone(), v.clone())))
93    }
94
95    pub fn inner(&self) -> &HashTrieMapSync<Value, Value> {
96        &self.inner
97    }
98}
99
100impl PartialEq for PersistentHashMap {
101    fn eq(&self, other: &Self) -> bool {
102        if self.count() != other.count() {
103            return false;
104        }
105        self.inner.iter().all(|(k, v)| other.get(k) == Some(v))
106    }
107}
108
109impl cljrs_gc::Trace for PersistentHashMap {
110    fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
111        for (k, v) in self.inner.iter() {
112            k.trace(visitor);
113            v.trace(visitor);
114        }
115    }
116
117    fn gc_size_extra(&self) -> usize {
118        // Per entry: Arc<(K,V)> allocation (16 overhead) + EntryWithHash (16)
119        // + tree-node share (~8). Values stored inline (not behind GcPtr).
120        let n = self.inner.size();
121        n * (40 + 2 * std::mem::size_of::<Value>())
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use crate::Value;
129    use crate::collections::array_map::AssocResult;
130    use cljrs_gc::GcPtr;
131
132    fn kw(s: &str) -> Value {
133        Value::Keyword(GcPtr::new(crate::keyword::Keyword::simple(s)))
134    }
135    fn int(n: i64) -> Value {
136        Value::Long(n)
137    }
138
139    #[test]
140    fn test_basic_ops() {
141        let m = PersistentHashMap::empty();
142        let m = m.assoc(kw("a"), int(1));
143        let m = m.assoc(kw("b"), int(2));
144        assert_eq!(m.count(), 2);
145        assert_eq!(m.get(&kw("a")), Some(&int(1)));
146        assert_eq!(m.get(&kw("b")), Some(&int(2)));
147        assert_eq!(m.get(&kw("c")), None);
148    }
149
150    #[test]
151    fn test_update() {
152        let m = PersistentHashMap::empty()
153            .assoc(kw("a"), int(1))
154            .assoc(kw("a"), int(99));
155        assert_eq!(m.count(), 1);
156        assert_eq!(m.get(&kw("a")), Some(&int(99)));
157    }
158
159    #[test]
160    fn test_dissoc() {
161        let m = PersistentHashMap::empty()
162            .assoc(kw("a"), int(1))
163            .assoc(kw("b"), int(2));
164        let m2 = m.dissoc(&kw("a"));
165        assert_eq!(m2.count(), 1);
166        assert_eq!(m2.get(&kw("a")), None);
167        assert_eq!(m2.get(&kw("b")), Some(&int(2)));
168    }
169
170    #[test]
171    fn test_many_entries() {
172        let mut m = PersistentHashMap::empty();
173        for i in 0i64..200 {
174            m = m.assoc(int(i), int(i * 10));
175        }
176        assert_eq!(m.count(), 200);
177        for i in 0i64..200 {
178            assert_eq!(m.get(&int(i)), Some(&int(i * 10)));
179        }
180    }
181
182    #[test]
183    fn test_merge() {
184        let a = PersistentHashMap::empty()
185            .assoc(kw("a"), int(1))
186            .assoc(kw("b"), int(2));
187        let b = PersistentHashMap::empty()
188            .assoc(kw("b"), int(99))
189            .assoc(kw("c"), int(3));
190        let merged = a.merge(&b);
191        assert_eq!(merged.count(), 3);
192        assert_eq!(merged.get(&kw("a")), Some(&int(1)));
193        assert_eq!(merged.get(&kw("b")), Some(&int(99))); // right wins
194        assert_eq!(merged.get(&kw("c")), Some(&int(3)));
195    }
196
197    #[test]
198    fn test_equality() {
199        let a = PersistentHashMap::empty()
200            .assoc(kw("a"), int(1))
201            .assoc(kw("b"), int(2));
202        let b = PersistentHashMap::empty()
203            .assoc(kw("b"), int(2))
204            .assoc(kw("a"), int(1));
205        assert_eq!(a, b);
206    }
207
208    #[test]
209    fn test_from_array_map() {
210        let mut am = PersistentArrayMap::empty();
211        for i in 0..3i64 {
212            let AssocResult::Array(next) = am.assoc(int(i), int(i * 2)) else {
213                panic!()
214            };
215            am = next;
216        }
217        let hm = PersistentHashMap::from_array_map(&am);
218        assert_eq!(hm.count(), 3);
219        for i in 0..3i64 {
220            assert_eq!(hm.get(&int(i)), Some(&int(i * 2)));
221        }
222    }
223}