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 }