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 }