1 /*
2  * Copyright (c) 2012-2020 The ANTLR Project. All rights reserved.
3  * Use of this file is governed by the BSD 3-clause license that
4  * can be found in the LICENSE.txt file in the project root.
5  */
6 
7 module antlr.v4.runtime.misc.Array2DHashSet;
8 
9 import std.array;
10 import std.variant;
11 import std.stdio;
12 import std.conv;
13 import std.math : floor;
14 import std.container.array;
15 import std.algorithm.mutation : remove;
16 import std.algorithm.searching : canFind;
17 import antlr.v4.runtime.misc;
18 
19 /**
20  * Set implementation with closed hashing (open addressing).
21  */
22 class Array2DHashSet(T)
23 {
24 
25     enum int INITAL_CAPACITY = 16;
26 
27     enum int INITAL_BUCKET_CAPACITY = 8;
28 
29     enum double LOAD_FACTOR = 0.75;
30 
31     protected size_t function(Object o) @trusted nothrow hashOfFp;
32 
33     protected bool function(Object a, Object b) opEqualsFp;
34 
35     protected T[][] buckets;
36 
37     /**
38      * @uml
39      * How many elements in set
40      */
41     protected int n = 0;
42 
43     /**
44      * @uml
45      * when to expand
46      */
47     protected int threshold = cast(int)(INITAL_CAPACITY * LOAD_FACTOR);
48 
49     /**
50      * @uml
51      * jump by 4 primes each expand or whatever
52      */
53     protected int currentPrime = 1;
54 
55     protected int initialBucketCapacity = INITAL_BUCKET_CAPACITY;
56 
57     public this()
58     {
59         this(null, null, INITAL_CAPACITY, INITAL_BUCKET_CAPACITY);
60     }
61 
62     public this(size_t function(Object o) @trusted nothrow hashOfFp,
63             bool function(Object a, Object b) opEqualsFp)
64     {
65         this(hashOfFp, opEqualsFp, INITAL_CAPACITY, INITAL_BUCKET_CAPACITY);
66     }
67 
68     public this(size_t function(Object o) @trusted nothrow hashOfFp, bool function(Object a, Object b) opEqualsFp,
69                 int initialCapacity, int initialBucketCapacity)
70     {
71         if (hashOfFp is null || opEqualsFp is null)
72         {
73             this.hashOfFp = &ObjectEqualityComparator.hashOf;
74             this.opEqualsFp = &ObjectEqualityComparator.opEquals;
75         }
76         else
77         {
78             this.hashOfFp = hashOfFp;
79             this.opEqualsFp = opEqualsFp;
80         }
81         this.buckets = createBuckets(initialCapacity);
82         this.initialBucketCapacity = initialBucketCapacity;
83     }
84 
85     /**
86      * @uml
87      * Add {@code o} to set if not there; return existing value if already
88      * there. This method performs the same operation as {@link #add} aside from
89      * the return value.
90      * @final
91      */
92     public final T getOrAdd(T o)
93     {
94         if (n > threshold)
95         {
96             expand();
97         }
98         return getOrAddImpl(o);
99     }
100 
101     protected T getOrAddImpl(T o)
102     {
103         auto b = getBucket(o);
104         T[] bucket = buckets[b];
105         // NEW BUCKET
106         if (bucket is null)
107         {
108             bucket = createBucket(initialBucketCapacity);
109             bucket[0] = o;
110             buckets[b] = bucket;
111             n++;
112             return o;
113         }
114 
115         // LOOK FOR IT IN BUCKET
116         for (int i = 0; i < bucket.length; i++)
117         {
118             auto existing = bucket[i];
119             if (!existing)
120             { // empty slot; not there, add.
121                 bucket[i] = o;
122                 n++;
123                 return o;
124             }
125             if (opEqualsFp(existing, o))
126             {
127                 return existing; // found existing, quit
128             }
129         }
130 
131         // FULL BUCKET, expand and add to end
132         auto oldLength = bucket.length;
133         bucket.length = bucket.length * 2;
134         buckets[b] = bucket;
135         bucket[oldLength] = o; // add to end
136         n++;
137         return o;
138     }
139 
140     public T get(T o)
141     {
142         if (o is null)
143             return o;
144         T nullElement;
145         auto b = getBucket(o);
146         T[] bucket = buckets[b];
147         if (bucket is null)
148             return nullElement; // no bucket
149         foreach (e; bucket)
150         {
151             if (e is null)
152                 return nullElement; // empty slot; not there
153             if (opEqualsFp(e, o))
154                 return e;
155         }
156         return nullElement;
157     }
158 
159     /**
160      * @uml
161      * @final
162      */
163     protected final size_t getBucket(T o)
164     {
165         return hashOfFp(o) & (buckets.length - 1); // assumes length is power of 2
166     }
167 
168     /**
169      * @uml
170      * @override
171      * @safe
172      * @nothrow
173      */
174     public override size_t toHash() @safe nothrow
175     {
176         size_t hash = MurmurHash.initialize();
177         foreach (bucket; buckets)
178         {
179             if (bucket is null)
180                 continue;
181             foreach (o; bucket)
182             {
183                 if (o is null)
184                     break;
185                 hash = MurmurHash.update(hash, hashOfFp(o));
186             }
187         }
188         hash = MurmurHash.finish(hash, size());
189         return hash;
190     }
191 
192     /**
193      * @uml
194      * @override
195      */
196     public override bool opEquals(Object o)
197     {
198         if (o is this)
199             return true;
200         if (!cast(Array2DHashSet) o)
201             return false;
202         Array2DHashSet!T other = cast(Array2DHashSet!T) o;
203         if (other.size() != size())
204             return false;
205         bool same = this.containsAll(other);
206         return same;
207     }
208 
209     protected void expand()
210     {
211         auto old = buckets;
212         currentPrime += 4;
213         int newCapacity = to!int(buckets.length) * 2;
214         auto newTable = createBuckets(newCapacity);
215         int[] newBucketLengths = new int[newTable.length];
216         buckets = newTable;
217         threshold = cast(int)(newCapacity * LOAD_FACTOR);
218         //      System.out.println("new size="+newCapacity+", thres="+threshold);
219         // rehash all existing entries
220         int oldSize = size();
221         foreach (bucket; old)
222         {
223             if (bucket is null)
224             {
225                 continue;
226             }
227             foreach (o; bucket)
228             {
229                 if (o is null)
230                 {
231                     break;
232                 }
233                 auto b = getBucket(o);
234                 int bucketLength = newBucketLengths[b];
235                 T[] newBucket;
236                 if (bucketLength == 0)
237                 {
238                     // new bucket
239                     newBucket = createBucket(initialBucketCapacity);
240                     newTable[b] = newBucket;
241                 }
242                 else
243                 {
244                     newBucket = newTable[b];
245                     if (bucketLength == newBucket.length)
246                     {
247                         // expand
248                         newBucket.length = newBucket.length * 2;
249                         newTable[b] = newBucket;
250                     }
251                 }
252 
253                 newBucket[bucketLength] = o;
254                 newBucketLengths[b]++;
255             }
256         }
257         assert(n == oldSize);
258     }
259 
260     /**
261      * @uml
262      * @final
263      */
264     public final bool add(T t)
265     {
266         T existing = getOrAdd(t);
267         return existing == t;
268     }
269 
270     /**
271      * @uml
272      * @final
273      */
274     public final int size()
275     {
276         return n;
277     }
278 
279     /**
280      * @uml
281      * @final
282      */
283     public final bool isEmpty()
284     {
285         return n == 0;
286     }
287 
288     /**
289      * @uml
290      * @final
291      */
292     public final bool contains(T o)
293     {
294         return containsFast(o);
295     }
296 
297     public bool containsFast(T obj)
298     {
299         if (obj is null)
300         {
301             return false;
302         }
303         return get(obj) !is null;
304     }
305 
306     public T[] toArray()
307     {
308         T[] a;
309         foreach (bucket; buckets)
310         {
311             if (bucket is null)
312             {
313                 continue;
314             }
315             foreach (o; bucket)
316             {
317                 if (o is null)
318                 {
319                     break;
320                 }
321                 a ~= o;
322             }
323         }
324         return a;
325     }
326 
327     public U[] toArray(U)(U[] a)
328     {
329         if (a.length < size())
330         {
331             a = Arrays.copyOf(a, size());
332         }
333         int i = 0;
334         foreach (T[] bucket; buckets)
335         {
336             if (bucket == null)
337             {
338                 continue;
339             }
340             foreach (T o; bucket)
341             {
342                 if (o is null)
343                 {
344                     break;
345                 }
346                 //@SuppressWarnings("unchecked") // array store will check this
347                 U targetElement = cast(U) o;
348                 a[i++] = targetElement;
349             }
350         }
351         return a;
352     }
353 
354     /**
355      * @uml
356      * @final
357      */
358     public final bool remove(T o)
359     {
360         return removeFast(o);
361     }
362 
363     public bool removeFast(T obj)
364     {
365         if (obj is null)
366         {
367             return false;
368         }
369         size_t b = getBucket(obj);
370         auto bucket = buckets[b];
371         if (bucket is null)
372         {
373             // no bucket
374             return false;
375         }
376         for (int i = 0; i < bucket.length; i++)
377         {
378             auto e = bucket[i];
379             if (e is null)
380             {
381                 // empty slot; not there
382                 return false;
383             }
384             if (opEqualsFp(e, obj))
385             { // found it
386                 // shift all elements to the right down one
387                 bucket.remove(i);
388                 bucket[$ - 1] = null;
389                 n--;
390                 return true;
391             }
392         }
393         return false;
394     }
395 
396     public bool containsAll(Object collection)
397     {
398         if (cast(Array2DHashSet!T) collection)
399         {
400             Array2DHashSet!T s = to!(Array2DHashSet!T)(collection);
401             foreach (bucket; s.buckets)
402             {
403                 if (bucket is null)
404                     continue;
405                 foreach (o; bucket)
406                 {
407                     if (o is null)
408                         break;
409                     if (!this.containsFast(o))
410                         return false;
411                 }
412             }
413         }
414         else
415         {
416             foreach (o; collection.tupleof)
417             {
418                 if (!this.containsFast(o))
419                     return false;
420             }
421             return false;
422         }
423         return true;
424     }
425 
426     public bool addAll(T[] c)
427     {
428         bool changed = false;
429         foreach (o; c)
430         {
431             T existing = getOrAdd(o);
432             if (existing != o)
433                 changed = true;
434         }
435         return changed;
436     }
437 
438     public bool retainAll(T[] c)
439     {
440         int newsize = 0;
441         foreach (bucket; buckets)
442         {
443             if (bucket is null)
444             {
445                 continue;
446             }
447             int i;
448             int j;
449             for (i = 0, j = 0; i < bucket.length; i++)
450             {
451                 if (bucket[i] is null)
452                 {
453                     break;
454                 }
455                 auto bg = bucket[i];
456                 if (!c.canFind(bg))
457                 {
458                     // removed
459                     continue;
460                 }
461                 // keep
462                 if (i != j)
463                 {
464                     bucket[j] = bucket[i];
465                 }
466                 j++;
467                 newsize++;
468             }
469             newsize += j;
470             while (j < i)
471             {
472                 bucket[j] = null;
473                 j++;
474             }
475         }
476 
477         bool changed = newsize != n;
478         n = newsize;
479         return changed;
480 
481     }
482 
483     public bool removeAll(T[] c)
484     {
485         bool changed = false;
486         foreach (o; c)
487         {
488             changed |= removeFast(o);
489         }
490         return changed;
491     }
492 
493     public void clear()
494     {
495         buckets = createBuckets(INITAL_CAPACITY);
496         n = 0;
497         threshold = to!int(floor(INITAL_CAPACITY * LOAD_FACTOR));
498     }
499 
500     /**
501      * @uml
502      * @override
503      */
504     public override string toString()
505     {
506         if (size == 0)
507             return "{}";
508         auto buf = appender!string;
509         buf.put('{');
510         bool first = true;
511         foreach (bucket; buckets)
512         {
513             if (bucket is null)
514                 continue;
515             foreach (o; bucket)
516             {
517                 if (o is null)
518                     break;
519                 if (first)
520                     first = false;
521                 else
522                     buf.put(", ");
523                 buf.put(to!string(o));
524             }
525         }
526         buf.put('}');
527         return buf.data;
528     }
529 
530     public string toTableString()
531     {
532         auto buf = appender!string;
533         foreach (bucket; buckets)
534         {
535             if (bucket is null)
536             {
537                 buf.put("null\n");
538                 continue;
539             }
540             buf.put('[');
541             bool first = true;
542             foreach (o; bucket)
543             {
544                 if (first)
545                     first = false;
546                 else
547                     buf.put(" ");
548                 if (o is null)
549                     buf.put("_");
550                 else
551                     buf.put(to!string(o));
552             }
553             buf.put("]\n");
554         }
555         return buf.data;
556     }
557 
558     public T[][] createBuckets(int capacity)
559     {
560         T[][] obj;
561         obj.length = capacity;
562         debug
563         {
564             writefln("T[][] obj -> %1$s, length -> %2$s", obj, obj.length);
565         }
566         return obj;
567     }
568 
569     public T[] createBucket(int capacity)
570     {
571         T[] obj;
572         obj.length = capacity;
573         return obj;
574     }
575 
576 }