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