1 /**
2  * This module defines a hash set data structure and various operations
3  * on it.
4  */
5 module dstruct.set;
6 
7 import dstruct.support;
8 import dstruct.map;
9 
10 /**
11  * A garbage collected implementation of a hash set.
12  *
13  * Because this type is a struct, it can never be null.
14  */
15 struct HashSet(T) {
16 private:
17     HashMap!(T, void[0]) _map;
18 public:
19     /**
20      * Add an element to this set if needed.
21      *
22      * Params:
23      *     value = The value to add to the set.
24      */
25     @safe pure nothrow
26     void add(ref T value) {
27         _map[value] = (void[0]).init;
28     }
29 
30 
31     /// ditto
32     @safe pure nothrow
33     void add(T value) {
34         add(value);
35     }
36 
37     /// A HashSet is an OutputRange.
38     alias put = add;
39 
40     /**
41      * Remove an element from this set if present.
42      *
43      * Params:
44      *     value = The value to remove from the set.
45      *
46      * Returns: true if a value was removed.
47      */
48     @nogc @safe pure nothrow
49     bool remove(ref T value) {
50         return _map.remove(value);
51     }
52 
53     /// ditto
54     @nogc @safe pure nothrow
55     bool remove(T value) {
56         return remove(value);
57     }
58 
59     /**
60      * Returns: The number of elements in this set.
61      */
62     @nogc @safe pure nothrow
63     @property size_t length() const {
64         return _map.length;
65     }
66 
67     /**
68      * Returns: True if this set is empty.
69      */
70     @nogc @safe pure nothrow
71     @property bool empty() const {
72         return length == 0;
73     }
74 
75     /**
76      * Provide the 'in' operator for sets.
77      *
78      * Test if the given value is present in the set.
79      *
80      * Params:
81      *    value = The value to test with.
82      *
83      *
84      * Returns: true if the value is in the set.
85      */
86     @nogc @safe pure nothrow
87     bool opBinaryRight(string op, T)(ref T value) const if(op == "in") {
88         return cast(bool)(value in _map);
89     }
90 
91     /// ditto
92     @nogc @safe pure nothrow
93     bool opBinaryRight(string op, T)(T value) const if(op == "in") {
94         return opBinaryRight!("in", T)(value);
95     }
96 
97     /**
98      * Returns: True if two sets contain all equal values.
99      */
100     @nogc @safe pure nothrow
101     bool opEquals(U)(const(HashSet!U) otherSet) const
102     if (is(U : T) || is(T : U)) {
103         static if (is(U : T)) {
104             if (this.length != otherSet.length) {
105                 return false;
106             }
107 
108             foreach(value; otherSet._map.keys()) {
109                 if (value !in this) {
110                     return false;
111                 }
112             }
113 
114             return true;
115         } else {
116             // Implement equality the other way by flipping things around.
117             return otherSet == this;
118         }
119     }
120 }
121 
122 // Test add and in.
123 unittest {
124     HashSet!int set;
125 
126     set.add(3);
127 
128     assert(3 in set);
129 }
130 
131 // Test !in
132 unittest {
133     HashSet!int set;
134 
135     set.add(3);
136 
137     assert(4 !in set);
138 }
139 
140 // Test remove
141 unittest {
142     HashSet!int set;
143 
144     set.add(4);
145 
146     assert(set.remove(4));
147     assert(!set.remove(3));
148 }
149 
150 // Test set length
151 unittest {
152     HashSet!int set;
153 
154     set.add(1);
155     set.add(2);
156     set.add(3);
157 
158     assert(set.length == 3);
159 }
160 
161 // Test empty length
162 unittest {
163     HashSet!int set;
164 
165     assert(set.length == 0);
166 }
167 
168 // Test basic equality.
169 unittest {
170     HashSet!int leftSet;
171     leftSet.add(2);
172     leftSet.add(3);
173 
174     HashSet!int rightSet;
175     rightSet.add(2);
176     rightSet.add(3);
177 
178     assert(leftSet == rightSet);
179 }
180 
181 // Test implicit conversion equality left to right.
182 unittest {
183     HashSet!int leftSet;
184     leftSet.add(2);
185     leftSet.add(3);
186 
187     HashSet!float rightSet;
188     rightSet.add(2.0);
189     rightSet.add(3.0);
190 
191     assert(leftSet == rightSet);
192 }
193 
194 // Test implicit conversion equality right to left.
195 unittest {
196     HashSet!float leftSet;
197     leftSet.add(2.0);
198     leftSet.add(3.0);
199 
200     HashSet!int rightSet;
201     rightSet.add(2);
202     rightSet.add(3);
203 
204     assert(leftSet == rightSet);
205 }
206 
207 /**
208  * Produce a range through all the entries of a set.
209  *
210  * Params:
211  *     set = A set.
212  *
213  * Returns:
214  *     A ForwardRange over all the entries in the set.
215  */
216 @nogc @safe pure nothrow
217 auto entries(U)(auto ref inout(HashSet!U) set) {
218     return set._map.keys();
219 }
220 
221 /**
222  * Returns: A mutable copy of this set.
223  */
224 @safe pure nothrow
225 HashSet!T dup(T)(ref const(HashSet!T) originalSet)
226 if(isDupable!T) {
227     HashSet!T newSet;
228 
229     foreach(value; originalSet.entries) {
230         newSet.add(value);
231     }
232 
233     return newSet;
234 }
235 
236 /// ditto
237 @safe pure nothrow
238 HashSet!T dup(T)(const(HashSet!T) originalSet)
239 if(isDupable!T) {
240     return originalSet.dup();
241 }
242 
243 unittest {
244     const(HashSet!int) createSet() {
245         HashSet!int set;
246 
247         set.add(1);
248         set.add(2);
249 
250         return set;
251     }
252 
253     auto set = createSet();
254     auto newSet = set.dup;
255     // Test r-values.
256     auto thirdSet = createSet().dup();
257 
258     assert(set == newSet);
259 }
260 
261 unittest {
262     import std.range;
263     import std.algorithm;
264 
265     HashSet!int set;
266 
267     repeat(cast(int) 3).take(3).copy(&set);
268     repeat(cast(int) 4).take(3).copy(&set);
269 
270     assert(set.length == 2);
271     assert(3 in set);
272     assert(4 in set);
273 }