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 }