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 }