cljrs_value/collections/
hash_set.rs1use crate::Value;
2use crate::collections::share_charge::{ShareCharge, hamt_path_bytes, rb_path_bytes};
3use rpds::{HashTrieMapSync, RedBlackTreeMapSync};
4
5#[derive(Debug, Clone)]
19pub struct PersistentHashSet {
20 index: HashTrieMapSync<Value, u64>,
21 order: RedBlackTreeMapSync<u64, Value>,
22 next_seq: u64,
23 charge: ShareCharge,
24 keys_inert: bool,
25}
26
27impl PersistentHashSet {
28 pub fn empty() -> Self {
29 Self {
30 index: HashTrieMapSync::new_sync(),
31 order: RedBlackTreeMapSync::new_sync(),
32 next_seq: 0,
33 charge: ShareCharge::full(),
34 keys_inert: true,
35 }
36 }
37
38 pub fn count(&self) -> usize {
39 self.index.size()
40 }
41
42 pub fn is_empty(&self) -> bool {
43 self.index.is_empty()
44 }
45
46 pub(crate) fn storage_entry(&self, key: &Value) -> Option<(usize, Value)> {
47 self.index
48 .get_key_value(key)
49 .map(|(stored, sequence)| (*sequence as usize, stored.clone()))
50 }
51 pub(crate) fn keys_inert(&self) -> bool {
52 self.keys_inert
53 }
54
55 pub fn contains(&self, val: &Value) -> bool {
56 self.index.contains_key(val)
57 }
58
59 pub fn conj(&self, val: Value) -> Self {
63 let keys_inert = self.keys_inert && crate::realization::is_inert(&val);
64 if self.index.contains_key(&val) {
65 return self.unchanged();
66 }
67 let seq = self.next_seq;
68 let n = self.count();
69 Self {
70 index: self.index.insert(val.clone(), seq),
71 order: self.order.insert(seq, val),
72 next_seq: seq + 1,
73 charge: self.charge.derive(n, n + 1, Self::path_bytes(n + 1)),
74 keys_inert,
75 }
76 }
77
78 pub fn conj_mut(&mut self, val: Value) -> &mut Self {
79 self.keys_inert &= crate::realization::is_inert(&val);
80 if !self.index.contains_key(&val) {
81 let seq = self.next_seq;
82 self.index.insert_mut(val.clone(), seq);
83 self.order.insert_mut(seq, val);
84 self.next_seq = seq + 1;
85 self.charge = ShareCharge::full();
86 }
87 self
88 }
89
90 pub(crate) fn disj_mut(&mut self, value: &Value) {
91 if let Some(seq) = self.index.get(value).copied() {
92 self.index.remove_mut(value);
93 self.order.remove_mut(&seq);
94 self.charge = ShareCharge::full();
95 }
96 }
97
98 pub fn disj(&self, val: &Value) -> Self {
100 match self.index.get(val) {
101 Some(seq) => {
102 let n = self.count();
103 Self {
104 index: self.index.remove(val),
105 order: self.order.remove(seq),
106 next_seq: self.next_seq,
107 charge: self.charge.derive(n, n - 1, Self::path_bytes(n)),
108 keys_inert: self.keys_inert,
109 }
110 }
111 None => self.unchanged(),
112 }
113 }
114
115 fn unchanged(&self) -> Self {
117 let n = self.count();
118 Self {
119 index: self.index.clone(),
120 order: self.order.clone(),
121 next_seq: self.next_seq,
122 charge: self.charge.derive(n, n, 0),
123 keys_inert: self.keys_inert,
124 }
125 }
126
127 fn path_bytes(n: usize) -> usize {
129 hamt_path_bytes::<(Value, u64)>(n) + rb_path_bytes::<(u64, Value)>(n)
130 }
131
132 pub(crate) fn full_size_extra(&self) -> usize {
134 self.count() * (88 + std::mem::size_of::<Value>())
137 }
138
139 #[cfg(test)]
142 fn charged(&self) -> usize {
143 self.charge.size_extra(self.full_size_extra())
144 }
145
146 pub fn iter(&self) -> impl Iterator<Item = &Value> {
148 self.order.iter().map(|(_, v)| v)
149 }
150}
151
152impl FromIterator<Value> for PersistentHashSet {
153 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
154 let mut s = PersistentHashSet::empty();
155 for v in iter {
156 s.conj_mut(v);
157 }
158 s
159 }
160}
161
162impl PartialEq for PersistentHashSet {
163 fn eq(&self, other: &Self) -> bool {
164 if self.count() != other.count() {
165 return false;
166 }
167 self.iter().all(|k| other.contains(k))
168 }
169}
170
171impl cljrs_gc::Trace for PersistentHashSet {
172 #[cfg(feature = "no-gc")]
173 fn immutable_children(&self) -> bool {
174 true
175 }
176 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
177 for (_, v) in self.order.iter() {
178 v.trace(visitor);
179 }
180 }
181
182 fn gc_size_extra(&self) -> usize {
183 self.charge.on_alloc(self.full_size_extra())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::Value;
191
192 fn int(n: i64) -> Value {
193 Value::Long(n)
194 }
195
196 #[test]
197 fn test_basic() {
198 let s = PersistentHashSet::empty();
199 let s = s.conj(int(1)).conj(int(2)).conj(int(3));
200 assert_eq!(s.count(), 3);
201 assert!(s.contains(&int(1)));
202 assert!(s.contains(&int(2)));
203 assert!(!s.contains(&int(99)));
204 }
205
206 #[test]
207 fn test_idempotent_conj() {
208 let s = PersistentHashSet::empty().conj(int(1)).conj(int(1));
209 assert_eq!(s.count(), 1);
210 }
211
212 #[test]
213 fn test_disj() {
214 let s = PersistentHashSet::empty().conj(int(1)).conj(int(2));
215 let s2 = s.disj(&int(1));
216 assert!(!s2.contains(&int(1)));
217 assert!(s2.contains(&int(2)));
218 assert_eq!(s2.count(), 1);
219 }
220
221 #[test]
222 fn test_equality_order_independent() {
223 let a = PersistentHashSet::from_iter([int(1), int(2), int(3)]);
224 let b = PersistentHashSet::from_iter([int(3), int(1), int(2)]);
225 assert_eq!(a, b);
226 }
227
228 #[test]
229 fn test_iteration_order_matches_insertion_order() {
230 let s = PersistentHashSet::empty()
231 .conj(int(10))
232 .conj(int(3))
233 .conj(int(7))
234 .conj(int(1));
235 let iterated: Vec<Value> = s.iter().cloned().collect();
236 assert_eq!(iterated, vec![int(10), int(3), int(7), int(1)]);
237 }
238
239 #[test]
240 fn conj_on_allocated_set_charges_path_bytes() {
241 let _alloc = crate::collections::share_charge::alloc_scope();
242 use cljrs_gc::GcPtr;
243
244 let s = GcPtr::new(PersistentHashSet::from_iter((0..10_000).map(int)));
245 let added = s.get().conj(int(-1));
246 assert_eq!(added.charged(), PersistentHashSet::path_bytes(10_001));
247 assert!(added.charged() * 100 < s.get().full_size_extra());
248 assert_eq!(s.get().conj(int(5)).charged(), 1);
250 let mut built = PersistentHashSet::empty();
252 built.conj_mut(int(1));
253 assert_eq!(built.charged(), built.full_size_extra());
254 }
255}