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 }