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 }