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