1 // Written in the D programming language.
2 
3 module swisstable.set;
4 
5 import swisstable.group;
6 import swisstable.hash;
7 import swisstable.table;
8 
9 import std.experimental.allocator.mallocator : Mallocator;
10 
11 /**
12 Swiss tables hash set
13 
14 Examples:
15 ---
16 // Initialize Set with capacity
17 auto set = Set!(string)(10);  // Default capacity is 0
18 set.insert("key");  // set.insert(["k1", "k2"]) also supported
19 if ("key" in set)
20   // do something
21 set.remove("key");  // set.remove(["k1", "k2"]) also supported
22 
23 set.length;
24 set.clear;
25 set.rehash;
26 set.dup;
27 K[] keys = set.keys;
28 foreach (k; set.byKey()) {}
29 ---
30 
31 Params:
32  T = element type
33  Allocator = Allocator type for internal allocation. Default is "shared const Mallocator"
34 
35 NOTE: Set doesn't guarantee pointer stability. Range and returned pointers are invalidated after insert/rehash.
36 
37 Copyright: Copyright 2024- Masahiro Nakagawa
38 Authros:   Masahiro Nakagawa
39 License:   $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache License Version 2.0)
40  */
41 struct Set(T, Allocator = shared const Mallocator)
42 {
43     import std.range : isInputRange, ElementType;
44     import std.traits : hasIndirections, isImplicitlyConvertible;
45 
46     // These string enums are used in Table template
47     enum CompCondition = "key == _slots[offset]";
48     enum HashKeyArg = "*slot";
49 
50     alias SlotType = T;
51     mixin Table!(Set, T, SlotType, Allocator, hasIndirections!T);
52 
53   public:
54     /**
55      * $(B key in set) syntax support.
56      *
57      * Returns: true if key exists. Otherwise false.
58      */
59     bool opBinaryRight(string op)(in T key) inout if (op == "in")
60     {
61         bool found;
62         size_t pos = find(key, found);
63 
64         return found;
65     }
66 
67     /**
68      * Insert a single element in the set.
69      *
70      * Returns: the number of elements inserted.
71      */    
72     size_t insert(E)(in E value) if (isImplicitlyConvertible!(E, SlotType))
73     {
74         import std.traits : Unqual;
75 
76         bool found;
77         size_t pos = findOrPrepareInsert(value, found);
78         if (found)
79             return 0;
80 
81         _slots[pos] = value;
82         return 1;
83     }
84 
85     /**
86      * Insert a range of element in the set.
87      *
88      * Returns: the number of elements inserted.
89      */    
90     size_t insert(E)(in E values) if (isInputRange!E && isImplicitlyConvertible!(ElementType!E, SlotType))
91     {
92         import std.traits : Unqual;
93 
94         size_t result;
95 
96         foreach (ref v; values) {
97             bool found;
98             size_t pos = findOrPrepareInsert(v, found);
99             if (found)
100                 continue;
101 
102             _slots[pos] = v;
103             ++result;
104         }
105 
106         return result;
107     }
108 
109     /**
110      * Remove value from the map.
111      *
112      * Returns: the number of elements removed.
113      */
114     size_t remove(E)(in E value) if (isImplicitlyConvertible!(E, SlotType))
115     {
116         if (_size == 0)
117             return 0;
118 
119         bool found;
120         size_t pos = find(value, found);
121         if (found) {
122             // destroy(_slots[pos]) is better?
123             destroy!(false)(_slots[pos]);
124 
125             // erase_meta_only in original implementation
126             --_size;
127             if (wasNeverFull(_control, _capacity, pos)) {
128                 setCtrl(_control, _capacity, pos, Control.Empty);
129                 growthInfo.overwriteFullAsEmpty();
130                 return 1;
131             }
132 
133             growthInfo.overwriteFullAsTombstone();
134             setCtrl(_control, _capacity, pos, Control.Tombstone);
135             return 1;
136         }
137 
138         return 0;
139     }
140 
141     /**
142      * Remove values from the map.
143      *
144      * Returns: the number of elements removed.
145      */
146     size_t remove(E)(in E values) if (isInputRange!E && isImplicitlyConvertible!(ElementType!E, SlotType))
147     {
148         if (_size == 0)
149             return 0;
150 
151         size_t result;
152         foreach (ref v; values)
153             result += remove(v);
154 
155         return result;
156     }
157 
158     /// Returns: a GC allocated dynamic array, the elements of which are the keys in the set.
159     @property T[] keys() const nothrow
160     {
161         return funcKeysValuesBody!(T, "*slot")();
162     }
163 
164     /// Returns: a forward range suitable for use as a $(B ForeachAggregate) which will iterate over the keys of the set.
165     Range byKey() nothrow
166     {
167         return typeof(return)(_control, _slots.ptr);
168     }
169 
170     /// $(B foreach) support
171     int opApply(scope int delegate(ref T) dg)
172     {
173         int result;
174         auto ctrl = _control;
175         auto slot = _slots.ptr;
176 
177         skipEmptyOrTombstoneSlots(ctrl, slot);
178         while (*ctrl != Control.Sentinel) {
179             result = dg(*slot);
180             if (result)
181                 break;
182 
183             ++ctrl;
184             ++slot;
185             skipEmptyOrTombstoneSlots(ctrl, slot);
186         }
187 
188         return result;
189     }
190 
191   private @trusted:
192     /// ForwardRange for byXXX functions
193     static struct Range
194     {
195       private:
196         Control* _control = emptyGroup;
197         SlotType* _slots;
198 
199       public nothrow @trusted:
200         @disable this();
201 
202         this(Control* ctrl, SlotType* slots)
203         {
204             _control = ctrl;
205             _slots = slots;
206             skipEmptyOrTombstoneSlots(_control, _slots);
207         }
208 
209         @property bool empty() const
210         {
211             return *_control == Control.Sentinel;
212         }
213 
214         @property ref SlotType front()
215         {
216             return *_slots;
217         }
218 
219         void popFront()
220         {
221             ++_control;
222             ++_slots;
223             skipEmptyOrTombstoneSlots(_control, _slots);
224         }
225 
226         typeof(this) save()
227         {
228             return typeof(this)(_control, _slots);
229         }
230     }
231 }
232 
233 unittest
234 {
235     Set!(string) set;
236 
237     assert(set.insert("k0") == 1);
238     assert(set.insert(["k1", "k2"]) == 2);
239 
240     assert(set.length == 3);
241     foreach (k; ["k0", "k1", "k2"])
242         assert(k in set);
243 
244     assert(set.remove("k0") == 1);
245     assert(set.remove(["k1", "k2"]) == 2);
246     assert(set.length == 0);
247 }
248 
249 unittest
250 {
251     // rehash
252     import std.algorithm : sort;
253     import std.conv : text;
254 
255     enum N = 100;
256     string[] keys = new string[](N);
257     Set!(string) set;
258 
259     foreach (i; 0..N) {
260         string k = text("k", i);
261         keys[i] = k;
262         set.insert(k);
263     }
264     assert(set.length == 100);
265 
266     foreach (i; 0..N / 2)
267         set.remove(keys[i]);
268     assert(set.length == N / 2);
269     assert(set.capacity == normalizeCapacity(N));
270 
271     set.rehash;
272 
273     assert(set.capacity == normalizeCapacity(N / 2));
274     foreach (i; N / 2..N)
275         assert(keys[i] in set);
276 
277     auto ks = set.keys.sort;
278     assert(ks[0] == "k50");
279     assert(ks[$ - 1] == "k99");
280 }
281 
282 unittest
283 {
284     // dup
285     import std.algorithm : sort, equal;
286     import std.conv : text;
287 
288     Set!(int) set;
289 
290     enum N = 100;
291     foreach (i; 0..N)
292         set.insert(i);
293 
294     auto nset = set.dup;
295     assert(nset.length == set.length);
296     assert(nset._capacity == set.capacity);
297     assert(equal(nset.keys.sort, set.keys.sort));
298 
299     nset.remove(10);
300     assert(10 !in nset);
301     assert(10 in set);
302 }
303 
304 unittest
305 {
306     // clear
307     import std.algorithm : sort, equal;
308     import std.range : empty;
309     import std.conv : text;
310 
311     Set!(int) set;
312 
313     enum N = 100;
314     foreach (i; 0..N)
315         set.insert(i);
316 
317     auto oldCap = set.capacity;
318     set.clear;
319 
320     assert(set.length == 0);
321     assert(set.capacity == oldCap);
322     assert(set.keys.empty);
323 
324     foreach (i; 0..10)
325         set.insert(i);
326     set.clear(false);
327 
328     assert(set.length == 0);
329     assert(set.capacity == 0);
330     assert(set.keys.empty);
331 }
332 
333 unittest
334 {
335     import std.algorithm : canFind;
336     import std.conv : text;
337     import std.range : walkLength;
338 
339     enum N = 100;
340     int[] keys = new int[](N);
341     Set!(int) set;
342 
343     foreach (ref k; set.byKey()) assert(false);
344 
345     foreach (i; 0..N) {
346         keys[i] = i;
347         set.insert(i);
348     }
349 
350     auto kr = set.byKey();
351     foreach (ref k; kr.save) {
352         assert(keys.canFind(k));
353         assert(k in set);
354     }
355     assert(walkLength(kr) == N);
356 
357     // opApply
358     int count = 0;
359     foreach (ref k; set) {
360         count++;
361         assert(keys.canFind(k));
362     }
363     assert(count == N);
364 }