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 }