1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* 6 * Internal package used by containers that rely on entries/nodes. 7 * 8 * Copyright: Eugene Wissner 2016-2022. 9 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 10 * Mozilla Public License, v. 2.0). 11 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 12 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/entry.d, 13 * tanya/container/entry.d) 14 */ 15 module tanya.container.entry; 16 17 import tanya.container.array; 18 import tanya.memory.allocator; 19 import tanya.memory.lifetime; 20 import tanya.meta.trait; 21 import tanya.meta.transform; 22 23 package struct SEntry(T) 24 { 25 // Item content. 26 T content; 27 28 // Next item. 29 SEntry* next; 30 } 31 32 package struct DEntry(T) 33 { 34 // Item content. 35 T content; 36 37 // Previous and next item. 38 DEntry* next, prev; 39 } 40 41 package enum BucketStatus : byte 42 { 43 deleted = -1, 44 empty = 0, 45 used = 1, 46 } 47 48 package struct Bucket(K, V = void) 49 { 50 static if (is(V == void)) 51 { 52 K key_; 53 } 54 else 55 { 56 package struct KV 57 { 58 package K key; 59 package V value; 60 } 61 KV kv; 62 } 63 BucketStatus status = BucketStatus.empty; 64 65 this()(ref K key) 66 { 67 this.key = key; 68 } 69 70 @property void key()(ref K key) 71 { 72 this.key() = key; 73 this.status = BucketStatus.used; 74 } 75 76 @property ref inout(K) key() inout 77 { 78 static if (is(V == void)) 79 { 80 return this.key_; 81 } 82 else 83 { 84 return this.kv.key; 85 } 86 } 87 88 void moveKey(ref K key) 89 { 90 move(key, this.key()); 91 this.status = BucketStatus.used; 92 } 93 94 bool opEquals(T)(ref const T key) const 95 { 96 return this.status == BucketStatus.used && this.key == key; 97 } 98 99 bool opEquals(ref const(typeof(this)) that) const 100 { 101 return key == that.key && this.status == that.status; 102 } 103 104 void remove() 105 { 106 static if (hasElaborateDestructor!K) 107 { 108 destroy(key); 109 } 110 this.status = BucketStatus.deleted; 111 } 112 } 113 114 // Possible sizes for the hash-based containers. 115 package static immutable size_t[33] primes = [ 116 0, 3, 7, 13, 23, 37, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 117 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 118 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 119 805306457, 1610612741, 3221225473 120 ]; 121 122 package(tanya.container) struct HashArray(alias hasher, K, V = void) 123 { 124 alias Key = K; 125 alias Value = V; 126 alias Bucket = .Bucket!(Key, Value); 127 alias Buckets = Array!Bucket; 128 129 Buckets array; 130 size_t lengthIndex; 131 size_t length; 132 133 this(shared Allocator allocator) 134 in 135 { 136 assert(allocator !is null); 137 } 138 do 139 { 140 this.array = Buckets(allocator); 141 } 142 143 this(T)(ref T data, shared Allocator allocator) 144 if (is(Unqual!T == HashArray)) 145 in 146 { 147 assert(allocator !is null); 148 } 149 do 150 { 151 this.array = Buckets(data.array, allocator); 152 this.lengthIndex = data.lengthIndex; 153 this.length = data.length; 154 } 155 156 // Move constructor 157 void move(ref HashArray data, shared Allocator allocator) 158 in 159 { 160 assert(allocator !is null); 161 } 162 do 163 { 164 this.array = Buckets(.move(data.array), allocator); 165 this.lengthIndex = data.lengthIndex; 166 this.length = data.length; 167 } 168 169 void swap(ref HashArray data) 170 { 171 .swap(this.array, data.array); 172 .swap(this.lengthIndex, data.lengthIndex); 173 .swap(this.length, data.length); 174 } 175 176 void opAssign()(ref typeof(this) that) 177 { 178 this.array = that.array; 179 this.lengthIndex = that.lengthIndex; 180 this.length = that.length; 181 } 182 183 @property size_t bucketCount() const 184 { 185 return primes[this.lengthIndex]; 186 } 187 188 /* 189 * Returns bucket position for `hash`. `0` may mean the 0th position or an 190 * empty `buckets` array. 191 */ 192 size_t locateBucket(T)(ref const T key) const 193 { 194 return this.array.length == 0 ? 0 : hasher(key) % bucketCount; 195 } 196 197 /* 198 * If the key doesn't already exists, returns an empty bucket the key can 199 * be inserted in and adjusts the element count. Otherwise returns the 200 * bucket containing the key. 201 */ 202 ref Bucket insert(ref Key key) 203 { 204 const newLengthIndex = this.lengthIndex + 1; 205 if (newLengthIndex != primes.length) 206 { 207 foreach (ref e; this.array[locateBucket(key) .. $]) 208 { 209 if (e == key) 210 { 211 return e; 212 } 213 else if (e.status != BucketStatus.used) 214 { 215 ++this.length; 216 return e; 217 } 218 } 219 220 this.rehashToSize(newLengthIndex); 221 } 222 223 foreach (ref e; this.array[locateBucket(key) .. $]) 224 { 225 if (e == key) 226 { 227 return e; 228 } 229 else if (e.status != BucketStatus.used) 230 { 231 ++this.length; 232 return e; 233 } 234 } 235 236 this.array.length = this.array.length + 1; 237 ++this.length; 238 return this.array[$ - 1]; 239 } 240 241 // Takes an index in the primes array. 242 void rehashToSize(const size_t n) 243 in 244 { 245 assert(n < primes.length); 246 } 247 do 248 { 249 auto storage = typeof(this.array)(primes[n], this.array.allocator); 250 DataLoop: foreach (ref e1; this.array[]) 251 { 252 if (e1.status == BucketStatus.used) 253 { 254 auto bucketPosition = hasher(e1.key) % primes[n]; 255 256 foreach (ref e2; storage[bucketPosition .. $]) 257 { 258 if (e2.status != BucketStatus.used) // Insert the key 259 { 260 .move(e1, e2); 261 continue DataLoop; 262 } 263 } 264 storage.insertBack(.move(e1)); 265 } 266 } 267 .move(storage, this.array); 268 this.lengthIndex = n; 269 } 270 271 void rehash(const size_t n) 272 { 273 size_t lengthIndex; 274 for (; lengthIndex < primes.length; ++lengthIndex) 275 { 276 if (primes[lengthIndex] >= n) 277 { 278 break; 279 } 280 } 281 if (lengthIndex > this.lengthIndex) 282 { 283 this.rehashToSize(lengthIndex); 284 } 285 } 286 287 @property size_t capacity() const 288 { 289 return this.array.length; 290 } 291 292 void clear() 293 { 294 this.array.clear(); 295 this.length = 0; 296 } 297 298 size_t remove(ref Key key) 299 { 300 foreach (ref e; this.array[locateBucket(key) .. $]) 301 { 302 if (e == key) // Found. 303 { 304 e.remove(); 305 --this.length; 306 return 1; 307 } 308 else if (e.status == BucketStatus.empty) 309 { 310 break; 311 } 312 } 313 return 0; 314 } 315 316 bool opBinaryRight(string op : "in", T)(ref const T key) const 317 { 318 foreach (ref e; this.array[locateBucket(key) .. $]) 319 { 320 if (e == key) // Found. 321 { 322 return true; 323 } 324 else if (e.status == BucketStatus.empty) 325 { 326 break; 327 } 328 } 329 return false; 330 } 331 }