1 /**
2  * This module defines a hash set data structure and various operations
3  * on it.
4  */
5 module dstruct.set;
6 
7 import std.traits : Unqual;
8 
9 import dstruct.support;
10 import dstruct.map;
11 
12 /**
13  * A garbage collected implementation of a hash set.
14  *
15  * Because this type is a struct, it can never be null.
16  */
17 struct HashSet(T) if (isAssignmentCopyable!(Unqual!T)) {
18 private:
19     HashMap!(T, void[0]) _map;
20 public:
21      /**
22      * Construct a set reserving a minimum of :minimumSize: space
23      * for the bucket list. The actual space allocated may be some number
24      * larger than the requested size, but it will be enough to fit
25      * as many items as requested without another allocation.
26      *
27      * Params:
28      * minimumSize = The minimum size for the hashmap.
29      */
30     @safe pure nothrow
31     this(size_t minimumSize) {
32         _map = typeof(_map)(minimumSize);
33     }
34 
35     /**
36      * Add an element to this set if needed.
37      *
38      * Params:
39      *     value = The value to add to the set.
40      */
41     @safe pure nothrow
42     void add(ref T value) {
43         _map[value] = (void[0]).init;
44     }
45 
46 
47     /// ditto
48     @safe pure nothrow
49     void add(T value) {
50         add(value);
51     }
52 
53     /// A HashSet is an OutputRange.
54     alias put = add;
55 
56     /**
57      * Remove an element from this set if present.
58      *
59      * Params:
60      *     value = The value to remove from the set.
61      *
62      * Returns: true if a value was removed.
63      */
64     @nogc @safe pure nothrow
65     bool remove(ref T value) {
66         return _map.remove(value);
67     }
68 
69     /// ditto
70     @nogc @safe pure nothrow
71     bool remove(T value) {
72         return remove(value);
73     }
74 
75     /**
76      * Returns: The number of elements in this set.
77      */
78     @nogc @safe pure nothrow
79     @property size_t length() const {
80         return _map.length;
81     }
82 
83     /**
84      * Returns: True if this set is empty.
85      */
86     @nogc @safe pure nothrow
87     @property bool empty() const {
88         return _map.empty;
89     }
90 
91     /**
92      * Implement boolean conversion for a set.
93      *
94      * Returns: True if this set is not empty.
95      */
96     @nogc @safe pure nothrow
97     bool opCast(T: bool)() const {
98         return !empty;
99     }
100 
101     /**
102      * Provide the 'in' operator for sets.
103      *
104      * Test if the given value is present in the set.
105      *
106      * Params:
107      *    value = The value to test with.
108      *
109      *
110      * Returns: true if the value is in the set.
111      */
112     @nogc @safe pure nothrow
113     bool opBinaryRight(string op, T)(ref T value) const if(op == "in") {
114         return cast(bool)(value in _map);
115     }
116 
117     /// ditto
118     @nogc @safe pure nothrow
119     bool opBinaryRight(string op, T)(T value) const if(op == "in") {
120         return opBinaryRight!("in", T)(value);
121     }
122 
123     /**
124      * Returns: True if two sets contain all equal values.
125      */
126     bool opEquals(U)(const(HashSet!U) otherSet) const
127     if (is(U : T) || is(T : U)) {
128         static if (is(U : T)) {
129             if (this.length != otherSet.length) {
130                 return false;
131             }
132 
133             foreach(value; otherSet._map.byKey()) {
134                 if (value !in this) {
135                     return false;
136                 }
137             }
138 
139             return true;
140         } else {
141             // Implement equality the other way by flipping things around.
142             return otherSet == this;
143         }
144     }
145 
146     static if(isDupable!T) {
147         /**
148          * Returns: A mutable copy of this set.
149          */
150         @safe pure nothrow
151         HashSet!T dup() const {
152             HashSet!T newSet;
153             newSet._map = _map.dup;
154 
155             return newSet;
156         }
157     }
158 }
159 
160 // Test that is is not possible to create a set with an element type which
161 // cannot be copy assigned.
162 unittest {
163     struct NonCopyable { @disable this(this); }
164 
165     assert(!__traits(compiles, HashSet!NonCopyable));
166     assert(__traits(compiles, HashSet!int));
167 }
168 
169 // Test add and in.
170 unittest {
171     HashSet!int set;
172 
173     set.add(3);
174 
175     assert(3 in set);
176 }
177 
178 // Test !in
179 unittest {
180     HashSet!int set;
181 
182     set.add(3);
183 
184     assert(4 !in set);
185 }
186 
187 // Test remove
188 unittest {
189     HashSet!int set;
190 
191     set.add(4);
192 
193     assert(set.remove(4));
194     assert(!set.remove(3));
195 }
196 
197 // Test set length
198 unittest {
199     HashSet!int set;
200 
201     set.add(1);
202     set.add(2);
203     set.add(3);
204 
205     assert(set.length == 3);
206 }
207 
208 // Test empty length
209 unittest {
210     HashSet!int set;
211 
212     assert(set.length == 0);
213 }
214 
215 // Set cast(bool) for a set
216 unittest {
217     @safe pure nothrow
218     void runTest() {
219         HashSet!int set;
220 
221         @nogc @safe pure nothrow
222         void runNoGCPart1(typeof(set) set) {
223             if (set) {
224                 assert(false, "cast(bool) failed for an empty set");
225             }
226         }
227 
228         @nogc @safe pure nothrow
229         void runNoGCPart2(typeof(set) set) {
230             if (!set) {
231                 assert(false, "cast(bool) failed for an non-empty set");
232             }
233         }
234 
235         @nogc @safe pure nothrow
236         void runNoGCPart3(typeof(set) set) {
237             if (set) {
238                 assert(false, "cast(bool) failed for an empty set");
239             }
240         }
241 
242         runNoGCPart1(set);
243         set.add(1);
244         runNoGCPart2(set);
245         set.remove(1);
246         runNoGCPart3(set);
247     }
248 
249     runTest();
250 }
251 
252 // Test basic equality.
253 unittest {
254     @safe pure nothrow
255     void runTest() {
256         HashSet!int leftSet;
257         leftSet.add(2);
258         leftSet.add(3);
259 
260         HashSet!int rightSet;
261         rightSet.add(2);
262         rightSet.add(3);
263 
264         // Test that @nogc works.
265         @nogc @safe pure nothrow
266         void runNoGCPart(typeof(leftSet) leftSet, typeof(rightSet) rightSet) {
267             assert(leftSet == rightSet);
268         }
269     }
270 
271     runTest();
272 }
273 
274 // Test implicit conversion equality left to right.
275 unittest {
276     HashSet!int leftSet;
277     leftSet.add(2);
278     leftSet.add(3);
279 
280     HashSet!float rightSet;
281     rightSet.add(2.0);
282     rightSet.add(3.0);
283 
284     assert(leftSet == rightSet);
285 }
286 
287 // Test implicit conversion equality right to left.
288 unittest {
289     HashSet!float leftSet;
290     leftSet.add(2.0);
291     leftSet.add(3.0);
292 
293     HashSet!int rightSet;
294     rightSet.add(2);
295     rightSet.add(3);
296 
297     assert(leftSet == rightSet);
298 }
299 
300 /**
301  * Produce a range through all the entries of a set.
302  *
303  * Params:
304  *     set = A set.
305  *
306  * Returns:
307  *     A ForwardRange over all the entries in the set.
308  */
309 @nogc @safe pure nothrow
310 auto entries(U)(auto ref inout(HashSet!U) set) {
311     return set._map.byKey();
312 }
313 
314 unittest {
315     const(HashSet!int) createSet() {
316         HashSet!int set;
317 
318         set.add(1);
319         set.add(2);
320 
321         return set;
322     }
323 
324     auto set = createSet();
325     auto newSet = set.dup;
326     // Test r-values.
327     auto thirdSet = createSet().dup();
328 
329     assert(set == newSet);
330 }
331 
332 unittest {
333     import std.range;
334     import std.algorithm;
335 
336     HashSet!int set;
337 
338     repeat(cast(int) 3).take(3).copy(&set);
339     repeat(cast(int) 4).take(3).copy(&set);
340 
341     assert(set.length == 2);
342     assert(3 in set);
343     assert(4 in set);
344 }