/* Copyright � 1999 CERN - European Organization for Nuclear Research. Permission to use, copy, modify, distribute and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. CERN makes no representations about the suitability of this software for any purpose. It is provided "as is" without expressed or implied warranty. */ package cern.colt.map; /** Status: Experimental; Do not use for production yet. Hash map holding (key,value) associations of type <tt>(int-->int)</tt>; Automatically grows and shrinks as needed; Implemented using open addressing with double hashing. First see the <a href="package-summary.html">package summary</a> and javadoc <a href="package-tree.html">tree view</a> to get the broad picture. Implements open addressing with double hashing, using "Brent's variation". Brent's variation slows insertions a bit down (not much) but reduces probes (collisions) for successful searches, in particular for large load factors. (It does not improve unsuccessful searches.) See D. Knuth, Searching and Sorting, 3rd ed., p.533-545 @author wolfgang.hoschek@cern.ch @version 1.0, 09/24/99 @see java.util.HashMap */ class QuickOpenIntIntHashMap extends OpenIntIntHashMap { public int totalProbesSaved = 0; // benchmark only /** * Constructs an empty map with default capacity and default load factors. */ public QuickOpenIntIntHashMap() { this(defaultCapacity); } /** * Constructs an empty map with the specified initial capacity and default load factors. * * @param initialCapacity the initial capacity of the map. * @throws IllegalArgumentException if the initial capacity is less * than zero. */ public QuickOpenIntIntHashMap(int initialCapacity) { this(initialCapacity, defaultMinLoadFactor, defaultMaxLoadFactor); } /** * Constructs an empty map with * the specified initial capacity and the specified minimum and maximum load factor. * * @param initialCapacity the initial capacity. * @param minLoadFactor the minimum load factor. * @param maxLoadFactor the maximum load factor. * @throws IllegalArgumentException if <tt>initialCapacity < 0 || (minLoadFactor < 0.0 || minLoadFactor >= 1.0) || (maxLoadFactor <= 0.0 || maxLoadFactor >= 1.0) || (minLoadFactor >= maxLoadFactor)</tt>. */ public QuickOpenIntIntHashMap(int initialCapacity, double minLoadFactor, double maxLoadFactor) { setUp(initialCapacity,minLoadFactor,maxLoadFactor); } /** * Associates the given key with the given value. * Replaces any old <tt>(key,someOtherValue)</tt> association, if existing. * * @param key the key the value shall be associated with. * @param value the value to be associated. * @return <tt>true</tt> if the receiver did not already contain such a key; * <tt>false</tt> if the receiver did already contain such a key - the new value has now replaced the formerly associated value. */ public boolean put(int key, int value) { /* This is open addressing with double hashing, using "Brent's variation". Brent's variation slows insertions a bit down (not much) but reduces probes (collisions) for successful searches, in particular for large load factors. (It does not improve unsuccessful searches.) See D. Knuth, Searching and Sorting, 3rd ed., p.533-545 h1(key) = hash % M h2(key) = decrement = Max(1, hash/M % M) M is prime = capacity = table.length probing positions are table[(h1-j*h2) % M] for j=0,1,... (M and h2 could also be chosen differently, but h2 is required to be relative prime to M.) */ int key0; final int tab[] = table; final byte stat[] = state; final int length = tab.length; int hash = HashFunctions.hash(key) & 0x7FFFFFFF; int i = hash % length; int decrement = (hash / length) % length; if (decrement == 0) decrement = 1; //System.out.println("insert search for (key,value)=("+key+","+value+") at i="+i+", dec="+decrement); // stop if we find a removed or free slot, or if we find the key itself // do NOT skip over removed slots (yes, open addressing is like that...) //int comp = comparisons; int t = 0; // the number of probes int p0 = i; // the first position to probe while (stat[i] == FULL && tab[i] != key) { t++; i -= decrement; //hashCollisions++; if (i<0) i+=length; } //if (comparisons-comp>0) System.out.println("probed "+(comparisons-comp)+" slots."); if (stat[i] == FULL) { // key already contained at slot i. this.values[i]=value; return false; } // not already contained, should be inserted at slot i. if (this.distinct > this.highWaterMark) { int newCapacity = chooseGrowCapacity(this.distinct+1,this.minLoadFactor, this.maxLoadFactor); //System.out.print("grow rehashing "); //System.out.println("at distinct="+distinct+", capacity="+table.length+" to newCapacity="+newCapacity+" ..."); rehash(newCapacity); return put(key, value); } /* Brent's variation does a local reorganization to reduce probes. It essentially means: We test whether it is possible to move the association we probed first (table[p0]) out of the way. If this is possible, it will reduce probes for the key to be inserted, since it takes its place; it gets hit earlier. However, future probes for the key that we move out of the way will increase. Thus we only move it out of the way, if we have a net gain, that is, if we save more probes than we loose. For the first probe we safe more than we loose if the number of probes we needed was >=2 (t>=2). If the first probe cannot be moved out of the way, we try the next probe (p1). Now we safe more than we loose if t>=3. We repeat this until we find that we cannot gain or that we can indeed move p(x) out of the way. Note: Under the great majority of insertions t<=1, so the loop is entered very infrequently. */ while (t>1) { //System.out.println("t="+t); key0 = tab[p0]; hash = HashFunctions.hash(key0) & 0x7FFFFFFF; decrement = (hash / length) % length; if (decrement == 0) decrement = 1; int pc = p0-decrement; // pc = (p0-j*decrement) % M, j=1,2,.. if (pc<0) pc += length; if (stat[pc] != FREE) { // not a free slot, continue searching for free slot to move to, or break. p0 = pc; t--; } else { // free or removed slot found, now move... //System.out.println("copying p0="+p0+" to pc="+pc+", (key,val)=("+tab[p0]+","+values[p0]+"), saving "+(t-1)+" probes."); this.totalProbesSaved += (t-1); tab[pc] = key0; stat[pc] = FULL; values[pc] = values[p0]; i = p0; // prepare to insert: table[p0]=key t = 0; // break loop } } //System.out.println("inserting at i="+i); this.table[i]=key; this.values[i]=value; if (this.state[i]==FREE) this.freeEntries--; this.state[i]=FULL; this.distinct++; if (this.freeEntries < 1) { //delta int newCapacity = chooseGrowCapacity(this.distinct+1,this.minLoadFactor, this.maxLoadFactor); rehash(newCapacity); } return true; } /** * Rehashes the contents of the receiver into a new table * with a smaller or larger capacity. * This method is called automatically when the * number of keys in the receiver exceeds the high water mark or falls below the low water mark. */ protected void rehash(int newCapacity) { int oldCapacity = table.length; //if (oldCapacity == newCapacity) return; int oldTable[] = table; int oldValues[] = values; byte oldState[] = state; int newTable[] = new int[newCapacity]; int newValues[] = new int[newCapacity]; byte newState[] = new byte[newCapacity]; this.lowWaterMark = chooseLowWaterMark(newCapacity,this.minLoadFactor); this.highWaterMark = chooseHighWaterMark(newCapacity,this.maxLoadFactor); this.table = newTable; this.values = newValues; this.state = newState; this.freeEntries = newCapacity-this.distinct; // delta int tmp = this.distinct; this.distinct = Integer.MIN_VALUE; // switch of watermarks for (int i = oldCapacity ; i-- > 0 ;) { if (oldState[i]==FULL) { put(oldTable[i], oldValues[i]); /* int element = oldTable[i]; int index = indexOfInsertion(element); newTable[index]=element; newValues[index]=oldValues[i]; newState[index]=FULL; */ } } this.distinct = tmp; } }