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