1 /*
2  * Copyright (c) 2012-2018 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     public static immutable int INITAL_CAPACITY = 16;
26 
27     public static immutable int INITAL_BUCKET_CAPACITY = 8;
28 
29     public static immutable 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, bool function(Object a, Object b) opEqualsFp)
63     {
64         this(hashOfFp, opEqualsFp, INITAL_CAPACITY, INITAL_BUCKET_CAPACITY);
65     }
66 
67     public this(size_t function(Object o) @trusted nothrow hashOfFp, bool function(Object a, Object b) opEqualsFp,
68                 int initialCapacity, int initialBucketCapacity)
69     {
70 	if (hashOfFp is null || opEqualsFp is null) {
71             this.hashOfFp = &ObjectEqualityComparator.hashOf;
72             this.opEqualsFp = &ObjectEqualityComparator.opEquals;
73         }
74         else {
75             this.hashOfFp = hashOfFp;
76             this.opEqualsFp = opEqualsFp;
77         }
78         this.buckets = createBuckets(initialCapacity);
79         this.initialBucketCapacity = initialBucketCapacity;
80     }
81 
82     /**
83      * @uml
84      * Add {@code o} to set if not there; return existing value if already
85      * there. This method performs the same operation as {@link #add} aside from
86      * the return value.
87      * @final
88      */
89     public final T getOrAdd(T o)
90     {
91         if (n > threshold) {
92             expand();
93         }
94         return getOrAddImpl(o);
95     }
96 
97     protected T getOrAddImpl(T o)
98     {
99         auto b = getBucket(o);
100         T[] bucket = buckets[b];
101         // NEW BUCKET
102         if (bucket is null) {
103             bucket = createBucket(initialBucketCapacity);
104             bucket[0] = o;
105             buckets[b] = bucket;
106             n++;
107             return o;
108         }
109 
110         // LOOK FOR IT IN BUCKET
111         for (int i=0; i < bucket.length; i++) {
112             auto existing = bucket[i];
113             if (!existing) { // empty slot; not there, add.
114                 bucket[i] = o;
115                 n++;
116                 return o;
117             }
118             if (opEqualsFp(existing, o)) {
119                 return existing; // found existing, quit
120             }
121         }
122 
123         // FULL BUCKET, expand and add to end
124         auto oldLength = bucket.length;
125         bucket.length = bucket.length * 2;
126         buckets[b] = bucket;
127         bucket[oldLength] = o; // add to end
128         n++;
129         return o;
130     }
131 
132     public T get(T o)
133     {
134 	if (o is null)
135             return o;
136         T nullElement;
137         auto b = getBucket(o);
138         T[] bucket = buckets[b];
139         if (bucket is null)
140             return nullElement; // no bucket
141         foreach (e; bucket) {
142             if (e is null)
143                 return nullElement; // empty slot; not there
144             if (opEqualsFp(e, o))
145                 return e;
146         }
147         return nullElement;
148     }
149 
150     /**
151      * @uml
152      * @final
153      */
154     protected final size_t getBucket(T o)
155     {
156         return hashOfFp(o) & (buckets.length - 1); // assumes length is power of 2
157     }
158 
159     /**
160      * @uml
161      * @override
162      * @safe
163      * @nothrow
164      */
165     public override size_t toHash() @safe nothrow
166     {
167 	size_t hash = MurmurHash.initialize();
168         foreach (bucket; buckets) {
169             if (bucket is null) continue;
170             foreach (o; bucket) {
171                 if (o is null) break;
172                 hash = MurmurHash.update(hash, hashOfFp(o));
173             }
174         }
175         hash = MurmurHash.finish(hash, size());
176         return hash;
177     }
178 
179     /**
180      * @uml
181      * @override
182      */
183     public override bool opEquals(Object o)
184     {
185 	if (o is this) return true;
186         if (o.classinfo != Array2DHashSet.classinfo) return false;
187         Array2DHashSet!T other = cast(Array2DHashSet!T)o;
188         if (other.size() != size()) return false;
189         bool same = this.containsAll(other);
190         return same;
191     }
192 
193     protected void expand()
194     {
195 	auto old = buckets;
196         currentPrime += 4;
197         int newCapacity = to!int(buckets.length) * 2;
198         auto newTable = createBuckets(newCapacity);
199         int[] newBucketLengths = new int[newTable.length];
200         buckets = newTable;
201         threshold = cast(int)(newCapacity * LOAD_FACTOR);
202         //		System.out.println("new size="+newCapacity+", thres="+threshold);
203         // rehash all existing entries
204         int oldSize = size();
205         foreach (bucket; old) {
206             if (bucket is null) {
207                 continue;
208             }
209             foreach (o; bucket) {
210                 if (o is null) {
211                     break;
212                 }
213                 auto b = getBucket(o);
214                 int bucketLength = newBucketLengths[b];
215                 T[] newBucket;
216                 if (bucketLength == 0) {
217                     // new bucket
218                     newBucket = createBucket(initialBucketCapacity);
219                     newTable[b] = newBucket;
220                 }
221                 else {
222                     newBucket = newTable[b];
223                     if (bucketLength == newBucket.length) {
224                         // expand
225                         newBucket.length = newBucket.length * 2;
226                         newTable[b] = newBucket;
227                     }
228                 }
229 
230                 newBucket[bucketLength] = o;
231                 newBucketLengths[b]++;
232             }
233         }
234         assert(n == oldSize);
235     }
236 
237     /**
238      * @uml
239      * @final
240      */
241     public final bool add(T t)
242     {
243 	T existing = getOrAdd(t);
244         return existing == t;
245     }
246 
247     /**
248      * @uml
249      * @final
250      */
251     public final int size()
252     {
253 	return n;
254     }
255 
256     /**
257      * @uml
258      * @final
259      */
260     public final bool isEmpty()
261     {
262         return n==0;
263     }
264 
265     /**
266      * @uml
267      * @final
268      */
269     public final bool contains(T o)
270     {
271         return containsFast(o);
272     }
273 
274     public bool containsFast(T obj)
275     {
276 	if (obj is null) {
277             return false;
278         }
279         return get(obj) !is null;
280     }
281 
282     public T[] toArray()
283     {
284 	T[] a;
285         foreach (bucket; buckets) {
286             if (bucket is null) {
287                 continue;
288             }
289             foreach (o; bucket) {
290                 if (o is null) {
291                     break;
292                 }
293                 a ~= o;
294             }
295         }
296         return a;
297     }
298 
299     public U[] toArray(U)(U[] a)
300     {
301 	if (a.length < size()) {
302             a = Arrays.copyOf(a, size());
303         }
304         int i = 0;
305         foreach (T[] bucket; buckets) {
306             if ( bucket==null ) {
307                 continue;
308             }
309             foreach (T o; bucket) {
310                 if (o is null) {
311                     break;
312                 }
313                 //@SuppressWarnings("unchecked") // array store will check this
314                 U targetElement = cast(U)o;
315                 a[i++] = targetElement;
316             }
317         }
318         return a;
319     }
320 
321     /**
322      * @uml
323      * @final
324      */
325     public final bool remove(T o)
326     {
327         return removeFast(o);
328     }
329 
330     public bool removeFast(T obj)
331     {
332 	if (obj is null) {
333             return false;
334         }
335         size_t b = getBucket(obj);
336         auto bucket = buckets[b];
337         if (bucket is null) {
338             // no bucket
339             return false;
340         }
341         for (int i=0; i<bucket.length; i++) {
342             auto e = bucket[i];
343             if (e is null) {
344                 // empty slot; not there
345                 return false;
346             }
347             if (opEqualsFp(e, obj) ) {  // found it
348                 // shift all elements to the right down one
349                 bucket.remove(i);
350                 bucket[$-1] = null;
351                 n--;
352                 return true;
353             }
354         }
355         return false;
356     }
357 
358     public bool containsAll(Object collection)
359     {
360 	if (collection.classinfo == Array2DHashSet!T.classinfo) {
361             Array2DHashSet!T s = to!(Array2DHashSet!T)(collection);
362             foreach (bucket; s.buckets) {
363                 if (bucket is null) continue;
364                 foreach (o; bucket) {
365                     if (o is null) break;
366                     if (!this.containsFast(o)) return false;
367                 }
368             }
369         }
370         else {
371             foreach (o; collection.tupleof) {
372                 if (!this.containsFast(o))
373                     return false;
374             }
375             return false;
376         }
377         return true;
378     }
379 
380     public bool addAll(T[] c)
381     {
382 	bool changed = false;
383         foreach (o; c) {
384             T existing = getOrAdd(o);
385             if (existing != o)
386                 changed = true;
387         }
388         return changed;
389     }
390 
391     public bool retainAll(T[] c)
392     {
393 	int newsize = 0;
394         foreach (bucket; buckets) {
395             if (bucket is null) {
396                 continue;
397             }
398             int i;
399             int j;
400             for (i = 0, j = 0; i < bucket.length; i++) {
401                 if (bucket[i] is null) {
402                     break;
403                 }
404                 auto bg = bucket[i];
405                 if (!c.canFind(bg)) {
406                     // removed
407                     continue;
408                 }
409                 // keep
410                 if (i != j) {
411                     bucket[j] = bucket[i];
412                 }
413                 j++;
414                 newsize++;
415             }
416             newsize += j;
417             while (j < i) {
418                 bucket[j] = null;
419                 j++;
420             }
421         }
422 
423         bool changed = newsize != n;
424         n = newsize;
425         return changed;
426 
427     }
428 
429     public bool removeAll(T[] c)
430     {
431         bool changed = false;
432         foreach (o; c) {
433             changed |= removeFast(o);
434         }
435         return changed;
436     }
437 
438     public void clear()
439     {
440 	buckets = createBuckets(INITAL_CAPACITY);
441         n = 0;
442         threshold = to!int(floor(INITAL_CAPACITY * LOAD_FACTOR));
443     }
444 
445     /**
446      * @uml
447      * @override
448      */
449     public override string toString()
450     {
451 	if (size == 0) return "{}";
452         auto buf = appender!string;
453         buf.put('{');
454         bool first = true;
455         foreach (bucket; buckets) {
456             if (bucket is null) continue;
457             foreach (o; bucket) {
458                 if (o is null) break;
459                 if ( first ) first=false;
460                 else buf.put(", ");
461                 buf.put(to!string(o));
462             }
463         }
464         buf.put('}');
465         return buf.data;
466     }
467 
468     public string toTableString()
469     {
470 	auto buf = appender!string;
471         foreach (bucket; buckets) {
472             if (bucket is null) {
473                 buf.put("null\n");
474                 continue;
475             }
476             buf.put('[');
477             bool first = true;
478             foreach (o; bucket) {
479                 if (first)
480                     first = false;
481                 else
482                     buf.put(" ");
483                 if (o is null) buf.put("_");
484                 else buf.put(to!string(o));
485             }
486             buf.put("]\n");
487         }
488         return buf.data;
489     }
490 
491     public T[][] createBuckets(int capacity)
492     {
493         T[][] obj;
494         obj.length = capacity;
495         debug {
496             writefln("T[][] obj -> %1$s, length -> %2$s", obj, obj.length);
497         }
498         return obj;
499     }
500 
501     public T[] createBucket(int capacity)
502     {
503         T[] obj;
504         obj.length = capacity;
505         return obj;
506     }
507 
508 }