1 /* 2 * SimpleHashSet.java 3 * This file is part of JaCoP. 4 * <p> 5 * JaCoP is a Java Constraint Programming solver. 6 * <p> 7 * Copyright (C) 2000-2008 Krzysztof Kuchcinski and Radoslaw Szymanek 8 * <p> 9 * This program is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU Affero General Public License as published by 11 * the Free Software Foundation, either version 3 of the License, or 12 * (at your option) any later version. 13 * <p> 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU Affero General Public License for more details. 18 * <p> 19 * Notwithstanding any other provision of this License, the copyright 20 * owners of this work supplement the terms of this License with terms 21 * prohibiting misrepresentation of the origin of this work and requiring 22 * that modified versions of this work be marked in reasonable ways as 23 * different from the original version. This supplement of the license 24 * terms is in accordance with Section 7 of GNU Affero General Public 25 * License version 3. 26 * <p> 27 * You should have received a copy of the GNU Affero General Public License 28 * along with this program. If not, see <http://www.gnu.org/licenses/>. 29 */ 30 31 package org.jacop.util; 32 33 import java.util.Arrays; 34 35 /** 36 * This class provides very simple HashSet functionality. Designed specially for 37 * maintaining pending constraints for evaluation. It's implementation was 38 * partially based on standard hash set implementation as implemented in java 39 * util class. 40 * 41 * @param <E> Class being stored in SimpleHashSet. 42 * @author Radoslaw Szymanek and Krzysztof Kuchcinski 43 * @version 4.8 44 */ 45 46 public class SimpleHashSet<E> { 47 48 @SuppressWarnings("hiding") class Entry<E> { 49 50 @SuppressWarnings("unchecked") public Entry chain; 51 52 public final E element; 53 54 @SuppressWarnings("unchecked") public Entry next; 55 56 /** 57 * Create new entry. 58 */ Entry(E el)59 Entry(E el) { 60 element = el; 61 next = null; 62 } 63 64 /** 65 * Create new entry. 66 */ Entry(E el, Entry<E> n)67 Entry(E el, Entry<E> n) { 68 element = el; 69 next = n; 70 } 71 add(E addedElement)72 @SuppressWarnings("unchecked") public boolean add(E addedElement) { 73 if (element == addedElement) 74 return false; 75 if (next != null) 76 return next.add(addedElement); 77 else { 78 next = new Entry(addedElement); 79 lastEntry.chain = next; 80 lastEntry = next; 81 return true; 82 } 83 } 84 contains(E checkedElement)85 @SuppressWarnings("unchecked") public boolean contains(E checkedElement) { 86 if (element == checkedElement) 87 return true; 88 if (next != null) 89 return next.contains(checkedElement); 90 else { 91 return false; 92 } 93 } 94 95 } 96 97 98 /** 99 * The default initial capacity - MUST be a power of two. 100 */ 101 static final int DEFAULT_INITIAL_CAPACITY = 16; 102 103 /** 104 * The load factor used when none specified in constructor. 105 */ 106 static final float DEFAULT_LOAD_FACTOR = 0.75f; 107 108 /** 109 * The maximum capacity, used if a higher value is implicitly specified by 110 * either of the constructors with arguments. MUST be a power of two <= 1<<30. 111 */ 112 static final int MAXIMUM_CAPACITY = 1 << 30; 113 114 /** 115 * Returns a hash value for the specified object. In addition to the 116 * object's own hashCode, this method applies a "supplemental hash 117 * function," which defends against poor quality hash functions. This is 118 * critical because SimpleHashSet uses power-of two length hash tables. 119 * <p> 120 * <p> 121 * The shift distances in this function were chosen as the result of an 122 * automated search over the entire four-dimensional search space. 123 * <p> 124 * This hash code function implementation is original Sun function proposed 125 * in util package. 126 */ 127 hash(Object x)128 static int hash(Object x) { 129 int h = x.hashCode(); 130 131 h += ~(h << 9); 132 h ^= (h >>> 14); 133 h += (h << 4); 134 h ^= (h >>> 10); 135 return h; 136 } 137 138 /** 139 * Returns index for hash code h. 140 */ indexFor(int h, int length)141 static int indexFor(int h, int length) { 142 return h & (length - 1); 143 } 144 145 /** 146 * It points to the first Entry to be removed. 147 */ 148 transient Entry<E> firstEntry = null; 149 150 /** 151 * The initial capacity for the hash set. 152 */ 153 int initialCapacity; 154 155 /** 156 * It points to the last Entry being add. 157 */ 158 transient Entry<E> lastEntry = null; 159 160 /** 161 * The load factor for the hash set. 162 */ 163 final float loadFactor; 164 165 /** 166 * The number of elements contained in this set. 167 */ 168 transient int size; 169 170 /** 171 * The set, resized as necessary. Length MUST Always be a power of two. 172 */ 173 @SuppressWarnings("unchecked") private transient Entry[] table; 174 175 /** 176 * The next size value at which to resize (capacity * load factor). 177 */ 178 int threshold; 179 180 /** 181 * Constructs an empty <tt>HashSet</tt> with the default initial capacity 182 * (16) and the default load factor (0.75). 183 */ SimpleHashSet()184 public SimpleHashSet() { 185 this.loadFactor = DEFAULT_LOAD_FACTOR; 186 threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 187 table = new Entry[DEFAULT_INITIAL_CAPACITY]; 188 initialCapacity = table.length; 189 } 190 191 /** 192 * Constructs an empty <tt>HashSet</tt> with the specified initial 193 * capacity and the default load factor (0.75). 194 * 195 * @param initialCapacity the initial capacity. 196 * @throws IllegalArgumentException if the initial capacity is negative. 197 */ SimpleHashSet(int initialCapacity)198 public SimpleHashSet(int initialCapacity) { 199 this(initialCapacity, DEFAULT_LOAD_FACTOR); 200 } 201 202 /** 203 * Constructs an empty <tt>HashSet</tt> with the specified initial 204 * capacity and load factor. 205 * 206 * @param initialCapacity The initial capacity. 207 * @param loadFactor The load factor. 208 * @throws IllegalArgumentException if the initial capacity is negative or the load factor is 209 * nonpositive. 210 */ SimpleHashSet(int initialCapacity, float loadFactor)211 public SimpleHashSet(int initialCapacity, float loadFactor) { 212 if (initialCapacity < 0) 213 throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); 214 if (initialCapacity > MAXIMUM_CAPACITY) 215 initialCapacity = MAXIMUM_CAPACITY; 216 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 217 throw new IllegalArgumentException("Illegal load factor: " + loadFactor); 218 219 // Find a power of 2 >= initialCapacity 220 int capacity = 1; 221 while (capacity < initialCapacity) 222 capacity <<= 1; 223 224 this.loadFactor = loadFactor; 225 this.threshold = (int) (capacity * loadFactor); 226 this.table = new Entry[capacity]; 227 this.initialCapacity = table.length; 228 } 229 230 /** 231 * Adds the specified element to this set. 232 * 233 * @param element element with which the specified value is to be associated. 234 * @return <tt>true</tt> if object is inserted and <tt>false</tt> if 235 * object was already in the set. 236 */ add(E element)237 @SuppressWarnings("unchecked") public boolean add(E element) { 238 int hash = hash(element); 239 int i = indexFor(hash, table.length); 240 241 Entry<E> e = table[i]; 242 243 if (e != null) { 244 boolean result = e.add(element); 245 246 if (result) { 247 // checks threshold and increases size 248 if (size++ >= threshold) 249 resize(2 * table.length); 250 } 251 return result; 252 } else { 253 e = table[i] = new Entry<E>(element); 254 255 if (firstEntry == null) { 256 firstEntry = e; 257 lastEntry = firstEntry; 258 } else { 259 lastEntry.chain = e; 260 lastEntry = e; 261 } 262 263 // checks threshold and increases size 264 if (size++ >= threshold) 265 resize(2 * table.length); 266 } 267 268 return true; 269 } 270 271 /** 272 * Removes all elements from this set. 273 */ clear()274 public void clear() { 275 276 // Entry<E>[] tab = table; 277 // for (int i = tab.length - 1; i >= 0; i--) 278 // tab[i] = null; 279 280 Arrays.fill(table, null); 281 282 firstEntry = null; 283 lastEntry = null; 284 285 size = 0; 286 } 287 288 /** 289 * Clones this set. 290 */ 291 clone()292 @Override @SuppressWarnings("unchecked") public Object clone() { 293 SimpleHashSet<E> result = new SimpleHashSet<E>(); 294 295 result.table = new Entry[table.length]; 296 result.size = size; 297 result.initialCapacity = result.table.length; 298 299 for (int i = table.length - 1; i >= 0; i--) { 300 Entry<E> e = table[i]; 301 if (e != null) { 302 result.table[i] = new Entry<E>(e.element); 303 e = e.next; 304 } 305 306 while (e != null) { 307 result.table[i].add(e.element); 308 e = e.next; 309 } 310 } 311 312 return result; 313 } 314 315 /** 316 * Returns the boolean value which specifies if given element is already in 317 * this identity hash set. 318 * 319 * @param element the element whose existence in the hash set is to be checked. 320 * @return the boolean value which specifies if given element exists in a 321 * hash set. 322 */ contains(E element)323 @SuppressWarnings({"unchecked"}) public boolean contains(E element) { 324 int hash = hash(element); 325 int i = indexFor(hash, table.length); 326 Entry<E> e = table[i]; 327 if (e != null) 328 return e.contains(element); 329 else 330 return false; 331 } 332 333 /** 334 * Returns <tt>true</tt> if this set contains no elements. 335 * 336 * @return <tt>true</tt> if this set contains no elements. 337 */ isEmpty()338 public boolean isEmpty() { 339 return size == 0; 340 } 341 342 /** 343 * Removes and returns an entry removed from the HashSet. Returns null if 344 * the HashSet contains no entry. 345 * 346 * @return the first entry which has been removed. 347 */ 348 removeFirst()349 @SuppressWarnings("unchecked") public E removeFirst() { 350 351 if (size == 0) 352 return null; 353 354 Entry<E> removed = firstEntry; 355 356 firstEntry = firstEntry.chain; 357 358 size--; 359 360 int hash = hash(removed.element); 361 int i = indexFor(hash, table.length); 362 363 if (removed.next == null) 364 table[i] = null; 365 else 366 table[i] = removed.next; 367 368 return removed.element; 369 370 } 371 372 /** 373 * Rehashes the contents of this set into a new array with a larger 374 * capacity. This method is called automatically when the number of elements 375 * in this set reaches its threshold. 376 * <p> 377 * If current capacity is MAXIMUM_CAPACITY, this method does not resize the 378 * set, but sets threshold to Integer.MAX_VALUE. This has the effect of 379 * preventing future calls. 380 * 381 * @param newCapacity the new capacity, MUST be a power of two; must be greater than 382 * current capacity unless current capacity is MAXIMUM_CAPACITY 383 * (in which case value is irrelevant). 384 */ resize(int newCapacity)385 @SuppressWarnings("unchecked") void resize(int newCapacity) { 386 387 Entry[] oldTable = table; 388 int oldCapacity = oldTable.length; 389 if (oldCapacity == MAXIMUM_CAPACITY) { 390 threshold = Integer.MAX_VALUE; 391 return; 392 } 393 394 table = new Entry[newCapacity]; 395 threshold = (int) (newCapacity * loadFactor); 396 transfer(oldTable); 397 398 } 399 400 /** 401 * Returns the number of elements in this set. 402 * 403 * @return the number of elements in this set. 404 */ size()405 public int size() { 406 return size; 407 } 408 409 /** 410 * Returns string representation of the hash set. 411 */ 412 toString()413 @Override @SuppressWarnings("unchecked") public String toString() { 414 StringBuffer S = new StringBuffer(); 415 416 S.append("SimpleHashSet["); 417 418 Entry[] tab = table; 419 420 for (int i = 0; i < tab.length; i++) { 421 422 Entry<E> e = tab[i]; 423 424 while (e != null) { 425 S.append(e.element); 426 e = e.next; 427 if (e != null) 428 S.append(","); 429 } 430 431 } 432 433 S.append("]"); 434 435 return S.toString(); 436 } 437 438 /** 439 * Transfer all entries from current table to newTable. 440 */ transfer(Entry[] oldTable)441 @SuppressWarnings("unchecked") void transfer(Entry[] oldTable) { 442 443 size = 0; 444 445 Entry<E> temp = firstEntry; 446 firstEntry = null; 447 448 while (temp != null) { 449 add(temp.element); 450 temp = temp.chain; 451 } 452 453 } 454 455 } 456