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 fn disj(&self, val: &Value) -> Self {
78 match self.index.get(val) {
79 Some(seq) => {
80 let n = self.count();
81 Self {
82 index: self.index.remove(val),
83 order: self.order.remove(seq),
84 next_seq: self.next_seq,
85 charge: self.charge.derive(n, n - 1, Self::path_bytes(n)),
86 }
87 }
88 None => self.unchanged(),
89 }
90 }
91
92 fn unchanged(&self) -> Self {
94 let n = self.count();
95 Self {
96 index: self.index.clone(),
97 order: self.order.clone(),
98 next_seq: self.next_seq,
99 charge: self.charge.derive(n, n, 0),
100 }
101 }
102
103 fn path_bytes(n: usize) -> usize {
105 hamt_path_bytes::<(Value, u64)>(n) + rb_path_bytes::<(u64, Value)>(n)
106 }
107
108 pub(crate) fn full_size_extra(&self) -> usize {
110 self.count() * (88 + std::mem::size_of::<Value>())
113 }
114
115 #[cfg(test)]
118 fn charged(&self) -> usize {
119 self.charge.size_extra(self.full_size_extra())
120 }
121
122 pub fn iter(&self) -> impl Iterator<Item = &Value> {
124 self.order.iter().map(|(_, v)| v)
125 }
126}
127
128impl FromIterator<Value> for PersistentHashSet {
129 fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
130 let mut s = PersistentHashSet::empty();
131 for v in iter {
132 s.conj_mut(v);
133 }
134 s
135 }
136}
137
138impl PartialEq for PersistentHashSet {
139 fn eq(&self, other: &Self) -> bool {
140 if self.count() != other.count() {
141 return false;
142 }
143 self.iter().all(|k| other.contains(k))
144 }
145}
146
147impl cljrs_gc::Trace for PersistentHashSet {
148 fn trace(&self, visitor: &mut cljrs_gc::MarkVisitor) {
149 for (_, v) in self.order.iter() {
150 v.trace(visitor);
151 }
152 }
153
154 fn gc_size_extra(&self) -> usize {
155 self.charge.on_alloc(self.full_size_extra())
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::Value;
163
164 fn int(n: i64) -> Value {
165 Value::Long(n)
166 }
167
168 #[test]
169 fn test_basic() {
170 let s = PersistentHashSet::empty();
171 let s = s.conj(int(1)).conj(int(2)).conj(int(3));
172 assert_eq!(s.count(), 3);
173 assert!(s.contains(&int(1)));
174 assert!(s.contains(&int(2)));
175 assert!(!s.contains(&int(99)));
176 }
177
178 #[test]
179 fn test_idempotent_conj() {
180 let s = PersistentHashSet::empty().conj(int(1)).conj(int(1));
181 assert_eq!(s.count(), 1);
182 }
183
184 #[test]
185 fn test_disj() {
186 let s = PersistentHashSet::empty().conj(int(1)).conj(int(2));
187 let s2 = s.disj(&int(1));
188 assert!(!s2.contains(&int(1)));
189 assert!(s2.contains(&int(2)));
190 assert_eq!(s2.count(), 1);
191 }
192
193 #[test]
194 fn test_equality_order_independent() {
195 let a = PersistentHashSet::from_iter([int(1), int(2), int(3)]);
196 let b = PersistentHashSet::from_iter([int(3), int(1), int(2)]);
197 assert_eq!(a, b);
198 }
199
200 #[test]
201 fn test_iteration_order_matches_insertion_order() {
202 let s = PersistentHashSet::empty()
203 .conj(int(10))
204 .conj(int(3))
205 .conj(int(7))
206 .conj(int(1));
207 let iterated: Vec<Value> = s.iter().cloned().collect();
208 assert_eq!(iterated, vec![int(10), int(3), int(7), int(1)]);
209 }
210
211 #[test]
212 fn conj_on_allocated_set_charges_path_bytes() {
213 let _alloc = crate::collections::share_charge::alloc_scope();
214 use cljrs_gc::GcPtr;
215
216 let s = GcPtr::new(PersistentHashSet::from_iter((0..10_000).map(int)));
217 let added = s.get().conj(int(-1));
218 assert_eq!(added.charged(), PersistentHashSet::path_bytes(10_001));
219 assert!(added.charged() * 100 < s.get().full_size_extra());
220 assert_eq!(s.get().conj(int(5)).charged(), 1);
222 let mut built = PersistentHashSet::empty();
224 built.conj_mut(int(1));
225 assert_eq!(built.charged(), built.full_size_extra());
226 }
227}