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 }