All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.carrotsearch.hppcrt.maps.ObjectDoubleOpenCustomHashMap Maven / Gradle / Ivy

Go to download

High Performance Primitive Collections Realtime (fork of HPPC from Carrotsearch) Fundamental data structures (maps, sets, lists, queues, heaps, sorts) generated for combinations of object and primitive types to conserve JVM memory and speed up execution. The Realtime fork intends to extend the existing collections, by tweaking to remove any dynamic allocations at runtime, and to obtain low variance execution times whatever the input nature.

There is a newer version: 0.7.5
Show newest version
package com.carrotsearch.hppcrt.maps;

import java.util.*;

import com.carrotsearch.hppcrt.*;
import com.carrotsearch.hppcrt.cursors.*;
import com.carrotsearch.hppcrt.predicates.*;
import com.carrotsearch.hppcrt.procedures.*;
import com.carrotsearch.hppcrt.strategies.*;

  
  
  
  
// If RH is defined, RobinHood Hashing is in effect :
  
/**
 * A hash map of KType to double, implemented using open
 * addressing with linear probing for collision resolution.
 *
 * The difference with {@link ObjectDoubleOpenHashMap} is that it uses a
 * {@link ObjectHashingStrategy} to compare objects externally instead of using
 * the built-in hashCode() /  equals(). In particular, the management of null
 * keys is up to the {@link ObjectHashingStrategy} implementation.
 * The internal buffers of this implementation ({@link #keys}), {@link #allocated})
 * are always allocated to the nearest size that is a power of two. When
 * the capacity exceeds the given load factor, the buffer size is doubled.
 * 

*

Important note. The implementation uses power-of-two tables and linear * probing, which may cause poor performance (many collisions) if hash values are * not properly distributed. Therefore, it is up to the {@link ObjectHashingStrategy} to * assure good performance.

* *

null keys support is up to the {@link ObjectHashingStrategy} implementation.

* * @author This code is inspired by the collaboration and implementation in the fastutil project. * *

Robin-Hood hashing algorithm is also used to minimize variance * in insertion and search-related operations, for an all-around smother operation at the cost * of smaller peak performance:

*

- Pedro Celis (1986) for the original Robin-Hood hashing paper,

*

- MoonPolySoft/Cliff Moon for the initial Robin-hood on HPPC implementation,

*

- Vincent Sonnier for the present implementation using cached hashes.

* */ @javax.annotation.Generated(date = "2014-10-25T20:54:05+0200", value = "HPPC-RT generated from: ObjectDoubleOpenCustomHashMap.java") public class ObjectDoubleOpenCustomHashMap implements ObjectDoubleMap, Cloneable { /** * Minimum capacity for the map. */ public final static int MIN_CAPACITY = HashContainerUtils.MIN_CAPACITY; /** * Default capacity. */ public final static int DEFAULT_CAPACITY = HashContainerUtils.DEFAULT_CAPACITY; /** * Default load factor. */ public final static float DEFAULT_LOAD_FACTOR = HashContainerUtils.DEFAULT_LOAD_FACTOR; protected double defaultValue = (0.0D); /** * Hash-indexed array holding all keys. *

Important! * The actual value in this field is always an instance of Object[]. * Be warned that javac emits additional casts when keys * are directly accessed; these casts * may result in exceptions at runtime. A workaround is to cast directly to * Object[] before accessing the buffer's elements (although it is highly * recommended to use a {@link #iterator()} instead. *

*

* Direct map iteration: iterate {keys[i], values[i]} for i in [0; keys.length[ where this.allocated[i] is true. *

*

Direct iteration warning: * If the iteration goal is to fill another hash container, please iterate {@link #keys} in reverse to prevent performance losses. * @see #values * @see #allocated */ public KType[] keys; /** * Hash-indexed array holding all values associated to the keys. * stored in {@link #keys}. *

Important! * The actual value in this field is always an instance of Object[]. * Be warned that javac emits additional casts when values * are directly accessed; these casts * may result in exceptions at runtime. A workaround is to cast directly to * Object[] before accessing the buffer's elements (although it is highly * recommended to use a {@link #iterator()} instead. * * * @see #keys */ public double[] values; /** * Information if an entry (slot) in the {@link #keys} table is allocated * or empty. * * In addition it caches hash value : If = -1, it means not allocated, else = HASH(keys[i]) & mask * for every index i. * * @see #assigned */ public int[] allocated; /** * Cached number of assigned slots in {@link #allocated}. */ protected int assigned; /** * The load factor for this map (fraction of allocated slots * before the buffers must be rehashed or reallocated). */ protected final float loadFactor; /** * Resize buffers when {@link #allocated} hits this value. */ protected int resizeAt; /** * The most recent slot accessed in {@link #containsKey} (required for * {@link #lget}). * * @see #containsKey * @see #lget */ protected int lastSlot; /** * Custom hashing strategy : * comparisons and hash codes of keys will be computed * with the strategy methods instead of the native Object equals() and hashCode() methods. */ protected final ObjectHashingStrategy hashStrategy; /** * Creates a hash map with the default capacity of {@value #DEFAULT_CAPACITY}, * load factor of {@value #DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link ObjectHashingStrategy} * *

See class notes about hash distribution importance.

*/ public ObjectDoubleOpenCustomHashMap(final ObjectHashingStrategy hashStrategy) { this(ObjectDoubleOpenCustomHashMap.DEFAULT_CAPACITY, hashStrategy); } /** * Creates a hash map with the given initial capacity, default load factor of * {@value #DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link ObjectHashingStrategy} * * @param initialCapacity Initial capacity (greater than zero and automatically * rounded to the next power of two). */ public ObjectDoubleOpenCustomHashMap(final int initialCapacity, final ObjectHashingStrategy hashStrategy) { this(initialCapacity, ObjectDoubleOpenCustomHashMap.DEFAULT_LOAD_FACTOR, hashStrategy); } /** * Creates a hash map with the given initial capacity, * load factor, using the hashStrategy as {@link ObjectHashingStrategy} * * @param initialCapacity Initial capacity (greater than zero and automatically * rounded to the next power of two). * * @param loadFactor The load factor (greater than zero and smaller than 1). * * */ public ObjectDoubleOpenCustomHashMap(final int initialCapacity, final float loadFactor, final ObjectHashingStrategy hashStrategy) { //only accept not-null strategies. if (hashStrategy != null) { this.hashStrategy = hashStrategy; } else { throw new IllegalArgumentException("ObjectDoubleOpenCustomHashMap() cannot have a null hashStrategy !"); } assert loadFactor > 0 && loadFactor <= 1 : "Load factor must be between (0, 1]."; this.loadFactor = loadFactor; //take into account of the load factor to garantee no reallocations before reaching initialCapacity. int internalCapacity = (int) (initialCapacity / loadFactor) + ObjectDoubleOpenCustomHashMap.MIN_CAPACITY; //align on next power of two internalCapacity = HashContainerUtils.roundCapacity(internalCapacity); this.keys = Internals.newArray(internalCapacity); this.values = new double [internalCapacity]; //fill with "not allocated" value this.allocated = new int[internalCapacity]; Internals.blankIntArrayMinusOne(this.allocated, 0, this.allocated.length); //Take advantage of the rounding so that the resize occur a bit later than expected. //allocate so that there is at least one slot that remains allocated = false //this is compulsory to guarantee proper stop in searching loops this.resizeAt = Math.max(3, (int) (internalCapacity * loadFactor)) - 2; } /** * Create a hash map from all key-value pairs of another container. */ public ObjectDoubleOpenCustomHashMap(final ObjectDoubleAssociativeContainer container, final ObjectHashingStrategy hashStrategy) { this(container.size(), hashStrategy); putAll(container); } /** * {@inheritDoc} */ @Override public double put(KType key, double value) { assert this.assigned < this.allocated.length; final int mask = this.allocated.length - 1; final ObjectHashingStrategy strategy = this.hashStrategy; int slot = Internals.rehash(strategy.computeHashCode(key)) & mask; final KType[] keys = this.keys; final double[] values = this.values; final int[] allocated = this.allocated; KType tmpKey; double tmpValue; int tmpAllocated; int initial_slot = slot; int dist = 0; int existing_distance = 0; while (allocated[slot] != -1 ) { if (strategy.equals(key, keys[slot])) { final double oldValue = values[slot]; values[slot] = value; return oldValue; } //re-shuffle keys to minimize variance existing_distance = (slot < allocated[slot] ? slot + allocated.length - allocated[slot] : slot - allocated[slot]); if (dist > existing_distance) { //swap current (key, value, initial_slot) with slot places tmpKey = keys[slot]; keys[slot] = key; key = tmpKey; tmpAllocated = allocated[slot]; allocated[slot] = initial_slot; initial_slot = tmpAllocated; tmpValue = values[slot]; values[slot] = value; value = tmpValue; dist = existing_distance; } slot = (slot + 1) & mask; dist++; } // Check if we need to grow. If so, reallocate new data, fill in the last element // and rehash. if (this.assigned == this.resizeAt) { expandAndPut(key, value, slot); } else { this.assigned++; allocated[slot] = initial_slot; keys[slot] = key; values[slot] = value; } return this.defaultValue; } /** * {@inheritDoc} */ @Override public int putAll(final ObjectDoubleAssociativeContainer container) { return putAll((Iterable>) container); } /** * {@inheritDoc} */ @Override public int putAll(final Iterable> iterable) { final int count = this.assigned; for (final ObjectDoubleCursor c : iterable) { put(c.key, c.value); } return this.assigned - count; } /** * {@inheritDoc} */ @Override public boolean putIfAbsent(final KType key, final double value) { if (!containsKey(key)) { put(key, value); return true; } return false; } /** * Trove-inspired API method. An equivalent * of the following code: *
     *  if (containsKey(key))
     *  {
     *      double v = (double) (lget() + additionValue);
     *      lset(v);
     *      return v;
     *  }
     *  else
     *  {
     *     put(key, putValue);
     *     return putValue;
     *  }
     * 
* * @param key The key of the value to adjust. * @param putValue The value to put if key does not exist. * @param additionValue The value to add to the existing value if key exists. * @return Returns the current value associated with key (after changes). */ @Override public double putOrAdd(KType key, double putValue, double additionValue) { assert assigned < allocated.length; final int mask = allocated.length - 1; final ObjectHashingStrategy strategy = this.hashStrategy; int slot = Internals.rehash(strategy.computeHashCode(key)) & mask; final int[] allocated = this.allocated; final KType[] keys = this.keys; final double[] values = this.values; double value = this.defaultValue; KType tmpKey; double tmpValue; int tmpAllocated; int initial_slot = slot; int dist = 0; int existing_distance = 0; while (allocated[slot] != -1 ) { existing_distance = (slot < allocated[slot] ? slot + allocated.length - allocated[slot] : slot - allocated[slot]); if (strategy.equals(key, keys[slot])) { values[slot] += additionValue; return values[slot]; } else if (dist > existing_distance) { //swap current (key, value, initial_slot) with slot places tmpKey = keys[slot]; keys[slot] = key; key = tmpKey; tmpValue = values[slot]; values[slot] = value; value = tmpValue; tmpAllocated = allocated[slot]; allocated[slot] = initial_slot; initial_slot = tmpAllocated; dist = existing_distance; } slot = (slot + 1) & mask; dist++; } if (assigned == resizeAt) { expandAndPut(key, putValue, slot); } else { assigned++; allocated[slot] = initial_slot; keys[slot] = key; values[slot] = putValue; } return putValue; } /** * An equivalent of calling *
     *  if (containsKey(key))
     *  {
     *      double v = (double) (lget() + additionValue);
     *      lset(v);
     *      return v;
     *  }
     *  else
     *  {
     *     put(key, additionValue);
     *     return additionValue;
     *  }
     * 
* * @param key The key of the value to adjust. * @param additionValue The value to put or add to the existing value if key exists. * @return Returns the current value associated with key (after changes). */ @Override public double addTo(KType key, double additionValue) { return putOrAdd(key, additionValue, additionValue); } /** * Expand the internal storage buffers (capacity) and rehash. */ private void expandAndPut(final KType pendingKey, final double pendingValue, final int freeSlot) { assert this.assigned == this.resizeAt; assert this.allocated[freeSlot] == -1; // Try to allocate new buffers first. If we OOM, it'll be now without // leaving the data structure in an inconsistent state. final KType[] oldKeys = this.keys; final double[] oldValues = this.values; final int[] oldAllocated = this.allocated; allocateBuffers(HashContainerUtils.nextCapacity(this.keys.length)); // We have succeeded at allocating new data so insert the pending key/value at // the free slot in the old arrays before rehashing. this.lastSlot = -1; this.assigned++; //We don't care of the oldAllocated value, so long it means "allocated = true", since the whole set is rebuilt from scratch. oldAllocated[freeSlot] = 1; oldKeys[freeSlot] = pendingKey; oldValues[freeSlot] = pendingValue; //for inserts final int mask = this.allocated.length - 1; final ObjectHashingStrategy strategy = this.hashStrategy; KType key = null; double value = (0.0D); int slot = -1; final KType[] keys = this.keys; final double[] values = this.values; final int[] allocated = this.allocated; KType tmpKey = null; double tmpValue = (0.0D); int tmpAllocated = -1; int initial_slot = -1; int dist = -1; int existing_distance = -1; //iterate all the old arrays to add in the newly allocated buffers //It is important to iterate backwards to minimize the conflict chain length ! for (int i = oldAllocated.length; --i >= 0;) { if (oldAllocated[i] != -1 ) { key = oldKeys[i]; value = oldValues[i]; slot = Internals.rehash(strategy.computeHashCode(key)) & mask; initial_slot = slot; dist = 0; while (allocated[slot] != -1 ) { //re-shuffle keys to minimize variance existing_distance = (slot < allocated[slot] ? slot + allocated.length - allocated[slot] : slot - allocated[slot]); if (dist > existing_distance) { //swap current (key, value, initial_slot) with slot places tmpKey = keys[slot]; keys[slot] = key; key = tmpKey; tmpAllocated = allocated[slot]; allocated[slot] = initial_slot; initial_slot = tmpAllocated; tmpValue = values[slot]; values[slot] = value; value = tmpValue; dist = existing_distance; } slot = (slot + 1) & mask; dist++; } //end while allocated[slot] = initial_slot; keys[slot] = key; values[slot] = value; } } } /** * Allocate internal buffers for a given capacity. * * @param capacity New capacity (must be a power of two). */ private void allocateBuffers(final int capacity) { final KType[] keys = Internals.newArray(capacity); final double[] values = new double [capacity]; final int[] allocated = new int[capacity]; Internals.blankIntArrayMinusOne(allocated, 0, allocated.length); this.keys = keys; this.values = values; this.allocated = allocated; //allocate so that there is at least one slot that remains allocated = false //this is compulsory to guarantee proper stop in searching loops this.resizeAt = Math.max(3, (int) (capacity * this.loadFactor)) - 2; } /** * {@inheritDoc} */ @Override public double remove(final KType key) { final int mask = this.allocated.length - 1; final ObjectHashingStrategy strategy = this.hashStrategy; int slot = Internals.rehash(strategy.computeHashCode(key)) & mask; int dist = 0; final KType[] keys = this.keys; final int[] states = this.allocated; while (states[slot] != -1 && dist <= (slot < states[slot] ? slot + states.length - states[slot] : slot - states[slot]) ) { if (strategy.equals(key, keys[slot])) { final double value = this.values[slot]; this.assigned--; shiftConflictingKeys(slot); return value; } slot = (slot + 1) & mask; dist++; } //end while true return this.defaultValue; } /** * Shift all the slot-conflicting keys allocated to (and including) slot. */ protected void shiftConflictingKeys(int slotCurr) { // Copied nearly verbatim from fastutil's impl. final int mask = this.allocated.length - 1; int slotPrev, slotOther; final ObjectHashingStrategy strategy = this.hashStrategy; final KType[] keys = this.keys; final double[] values = this.values; final int[] allocated = this.allocated; while (true) { slotCurr = ((slotPrev = slotCurr) + 1) & mask; while (allocated[slotCurr] != -1 ) { //use the cached value, no need to recompute slotOther = allocated[slotCurr]; if (slotPrev <= slotCurr) { // we're on the right of the original slot. if (slotPrev >= slotOther || slotOther > slotCurr) { break; } } else { // we've wrapped around. if (slotPrev >= slotOther && slotOther > slotCurr) { break; } } slotCurr = (slotCurr + 1) & mask; } if ( allocated[slotCurr] == -1 ) { break; } // Shift key/value/allocated triplet. keys[slotPrev] = keys[slotCurr]; values[slotPrev] = values[slotCurr]; allocated[slotPrev] = allocated[slotCurr]; } //means not allocated allocated[slotPrev] = -1; /* */ keys[slotPrev] = null; /* */ /* */ } /** * {@inheritDoc} */ @Override public int removeAll(final ObjectContainer container) { final int before = this.assigned; for (final ObjectCursor cursor : container) { remove(cursor.value); } return before - this.assigned; } /** * {@inheritDoc} *

Important! * If the predicate actually injects the removed keys in another hash container, you may experience performance losses. */ @Override public int removeAll(final ObjectPredicate predicate) { final int before = this.assigned; final KType[] keys = this.keys; final int[] states = this.allocated; for (int i = 0; i < states.length;) { if (states[i] != -1 ) { if (predicate.apply(keys[i])) { this.assigned--; shiftConflictingKeys(i); // Repeat the check for the same i. continue; } } i++; } return before - this.assigned; } /** * {@inheritDoc} * *

Use the following snippet of code to check for key existence * first and then retrieve the value if it exists.

*
     * if (map.containsKey(key))
     *   value = map.lget();
     * 
*/ @Override public double get(final KType key) { final int mask = this.allocated.length - 1; final ObjectHashingStrategy strategy = this.hashStrategy; int slot = Internals.rehash(strategy.computeHashCode(key)) & mask; int dist = 0; final KType[] keys = this.keys; final int[] states = this.allocated; while (states[slot] != -1 && dist <= (slot < states[slot] ? slot + states.length - states[slot] : slot - states[slot]) ) { if (strategy.equals(key, keys[slot])) { return this.values[slot]; } slot = (slot + 1) & mask; dist++; } //end while true return this.defaultValue; } /** * Returns the last key stored in this has map for the corresponding * most recent call to {@link #containsKey}. * Precondition : {@link #containsKey} must have been called previously ! *

Use the following snippet of code to check for key existence * first and then retrieve the key value if it exists.

*
     * if (map.containsKey(key))
     *   value = map.lkey();
     * 
* *

This is equivalent to calling:

*
     * if (map.containsKey(key))
     *   key = map.keys[map.lslot()];
     * 
*/ public KType lkey() { assert this.lastSlot >= 0 : "Call containsKey() first."; assert this.allocated[this.lastSlot] != -1 : "Last call to exists did not have any associated value."; return this.keys[this.lastSlot]; } /** * Returns the last value saved in a call to {@link #containsKey}. * Precondition : {@link #containsKey} must have been called previously ! * @see #containsKey */ public double lget() { assert this.lastSlot >= 0 : "Call containsKey() first."; assert this.allocated[this.lastSlot] != -1 : "Last call to exists did not have any associated value."; return this.values[this.lastSlot]; } /** * Sets the value corresponding to the key saved in the last * call to {@link #containsKey}, if and only if the key exists * in the map already. * Precondition : {@link #containsKey} must have been called previously ! * @see #containsKey * @return Returns the previous value stored under the given key. */ public double lset(final double key) { assert this.lastSlot >= 0 : "Call containsKey() first."; assert this.allocated[this.lastSlot] != -1 : "Last call to exists did not have any associated value."; final double previous = this.values[this.lastSlot]; this.values[this.lastSlot] = key; return previous; } /** * @return Returns the slot of the last key looked up in a call to {@link #containsKey} if * it returned true. * * @see #containsKey */ public int lslot() { assert this.lastSlot >= 0 : "Call containsKey() first."; return this.lastSlot; } /** * {@inheritDoc} * *

Saves the associated value for fast access using {@link #lget} * or {@link #lset}.

*
     * if (map.containsKey(key))
     *   value = map.lget();
     * 
* or, to modify the value at the given key without looking up * its slot twice: *
     * if (map.containsKey(key))
     *   map.lset(map.lget() + 1);
     * 
* or, to retrieve the key-equivalent object from the map: *
     * if (map.containsKey(key))
     *   map.lkey();
     * 
*/ @Override public boolean containsKey(final KType key) { final int mask = this.allocated.length - 1; final ObjectHashingStrategy strategy = this.hashStrategy; int slot = Internals.rehash(strategy.computeHashCode(key)) & mask; int dist = 0; final KType[] keys = this.keys; final int[] states = this.allocated; while (states[slot] != -1 && dist <= (slot < states[slot] ? slot + states.length - states[slot] : slot - states[slot]) ) { if (strategy.equals(key, keys[slot])) { this.lastSlot = slot; return true; } slot = (slot + 1) & mask; dist++; } //end while true //unsuccessful search this.lastSlot = -1; return false; } /** * {@inheritDoc} * *

Does not release internal buffers.

*/ @Override public void clear() { this.assigned = 0; this.lastSlot = -1; // States are always cleared. Internals.blankIntArrayMinusOne(this.allocated, 0, this.allocated.length); //Faster than Arrays.fill(keys, null); // Help the GC. ObjectArrays.blankArray(this.keys, 0, this.keys.length); } /** * {@inheritDoc} */ @Override public int size() { return this.assigned; } /** * {@inheritDoc} */ @Override public int capacity() { return this.resizeAt - 1; } /** * {@inheritDoc} * *

Note that an empty container may still contain many deleted keys (that occupy buffer * space). Adding even a single element to such a container may cause rehashing.

*/ @Override public boolean isEmpty() { return size() == 0; } /** * {@inheritDoc} */ @Override public int hashCode() { int h = 0; final KType[] keys = this.keys; final double[] values = this.values; final int[] states = this.allocated; final ObjectHashingStrategy strategy = this.hashStrategy; for (int i = states.length; --i >= 0;) { if (states[i] != -1 ) { //This hash is an intrinsic property of the container contents, //consequently is independent from the HashStrategy, so do not use it ! h += Internals.rehash(strategy.computeHashCode(keys[i])) + Internals.rehash(values[i]); } } return h; } /** * this instance and obj can only be equal if :
     * (both are ObjectDoubleOpenCustomHashMap)
     * and
     * (both have equal hash strategies defined by {@link #ObjectHashingStrategy}.equals(obj.hashStrategy))
* then, both maps are compared using their {@link #ObjectHashingStrategy}. */ @Override public boolean equals(final Object obj) { if (obj != null) { if (obj == this) { return true; } if (!(obj instanceof ObjectDoubleOpenCustomHashMap)) { return false; } if (!this.hashStrategy.equals(((ObjectDoubleOpenCustomHashMap) obj).hashStrategy)) { return false; } @SuppressWarnings("unchecked") final ObjectDoubleOpenCustomHashMap other = (ObjectDoubleOpenCustomHashMap) obj; if (other.size() == this.size()) { final EntryIterator it = this.iterator(); while (it.hasNext()) { final ObjectDoubleCursor c = it.next(); if (other.containsKey(c.key)) { final double v = other.get(c.key); if ((Double.doubleToLongBits(c.value) == Double.doubleToLongBits(v))) { continue; } } //recycle it.release(); return false; } return true; } } return false; } /** * An iterator implementation for {@link #iterator}. */ public final class EntryIterator extends AbstractIterator> { public final ObjectDoubleCursor cursor; public EntryIterator() { this.cursor = new ObjectDoubleCursor(); this.cursor.index = -2; } /** * Iterate backwards w.r.t the buffer, to * minimize collision chains when filling another hash container (ex. with putAll()) */ @Override protected ObjectDoubleCursor fetch() { int i = this.cursor.index - 1; while (i >= 0 && ObjectDoubleOpenCustomHashMap.this.allocated[i] == -1 ) { i--; } if (i == -1) { return done(); } this.cursor.index = i; this.cursor.key = ObjectDoubleOpenCustomHashMap.this.keys[i]; this.cursor.value = ObjectDoubleOpenCustomHashMap.this.values[i]; return this.cursor; } } /** * internal pool of EntryIterator */ protected final IteratorPool, EntryIterator> entryIteratorPool = new IteratorPool, EntryIterator>( new ObjectFactory() { @Override public EntryIterator create() { return new EntryIterator(); } @Override public void initialize(final EntryIterator obj) { obj.cursor.index = ObjectDoubleOpenCustomHashMap.this.keys.length; } @Override public void reset(final EntryIterator obj) { // nothing } }); /** * {@inheritDoc} * @return */ @Override public EntryIterator iterator() { //return new EntryIterator(); return this.entryIteratorPool.borrow(); } /** * {@inheritDoc} */ @Override public > T forEach(final T procedure) { final KType[] keys = this.keys; final double[] values = this.values; final int[] states = this.allocated; //Iterate in reverse for side-stepping the longest conflict chain //in another hash, in case apply() is actually used to fill another hash container. for (int i = states.length - 1; i >= 0; i--) { if (states[i] != -1 ) { procedure.apply(keys[i], values[i]); } } return procedure; } /** * {@inheritDoc} */ @Override public > T forEach(final T predicate) { final KType[] keys = this.keys; final double[] values = this.values; final int[] states = this.allocated; //Iterate in reverse for side-stepping the longest conflict chain //in another hash, in case apply() is actually used to fill another hash container. for (int i = states.length - 1; i >= 0; i--) { if (states[i] != -1 ) { if (!predicate.apply(keys[i], values[i])) { break; } } } //end for return predicate; } /** * @return a new KeysContainer view of the keys of this associated container. * This view then reflects all changes from the map. */ @Override public KeysContainer keys() { return new KeysContainer(); } /** * A view of the keys inside this hash map. */ public final class KeysContainer extends AbstractObjectCollection implements ObjectLookupContainer { private final ObjectDoubleOpenCustomHashMap owner = ObjectDoubleOpenCustomHashMap.this; @Override public boolean contains(final KType e) { return containsKey(e); } @Override public > T forEach(final T procedure) { final KType[] keys = this.owner.keys; final int[] states = this.owner.allocated; //Iterate in reverse for side-stepping the longest conflict chain //in another hash, in case apply() is actually used to fill another hash container. for (int i = states.length - 1; i >= 0; i--) { if (states[i] != -1 ) { procedure.apply(keys[i]); } } return procedure; } @Override public > T forEach(final T predicate) { final KType[] keys = this.owner.keys; final int[] states = this.owner.allocated; //Iterate in reverse for side-stepping the longest conflict chain //in another hash, in case apply() is actually used to fill another hash container. for (int i = states.length - 1; i >= 0; i--) { if (states[i] != -1 ) { if (!predicate.apply(keys[i])) { break; } } } return predicate; } /** * {@inheritDoc} */ @Override public KeysIterator iterator() { //return new KeysIterator(); return this.keyIteratorPool.borrow(); } /** * {@inheritDoc} */ @Override public int size() { return this.owner.size(); } /** * {@inheritDoc} */ @Override public int capacity() { return this.owner.capacity(); } @Override public void clear() { this.owner.clear(); } @Override public int removeAll(final ObjectPredicate predicate) { return this.owner.removeAll(predicate); } @Override public int removeAllOccurrences(final KType e) { final boolean hasKey = this.owner.containsKey(e); int result = 0; if (hasKey) { this.owner.remove(e); result = 1; } return result; } /** * internal pool of KeysIterator */ protected final IteratorPool, KeysIterator> keyIteratorPool = new IteratorPool, KeysIterator>( new ObjectFactory() { @Override public KeysIterator create() { return new KeysIterator(); } @Override public void initialize(final KeysIterator obj) { obj.cursor.index = ObjectDoubleOpenCustomHashMap.this.keys.length; } @Override public void reset(final KeysIterator obj) { // nothing } }); @Override public KType[] toArray(final KType[] target) { final KType[] keys = this.owner.keys; final int[] states = this.owner.allocated; int count = 0; for (int i = 0; i < keys.length; i++) { if (states[i] != -1 ) { target[count++] = keys[i]; } } assert count == this.owner.assigned; return target; } }; /** * An iterator over the set of assigned keys. */ public final class KeysIterator extends AbstractIterator> { public final ObjectCursor cursor; public KeysIterator() { this.cursor = new ObjectCursor(); this.cursor.index = -2; } /** * Iterate backwards w.r.t the buffer, to * minimize collision chains when filling another hash container (ex. with putAll()) */ @Override protected ObjectCursor fetch() { int i = this.cursor.index - 1; while (i >= 0 && ObjectDoubleOpenCustomHashMap.this.allocated[i] == -1 ) { i--; } if (i == -1) { return done(); } this.cursor.index = i; this.cursor.value = ObjectDoubleOpenCustomHashMap.this.keys[i]; return this.cursor; } } /** * @return a new ValuesContainer, view of the values of this map. * This view then reflects all changes from the map. */ @Override public ValuesContainer values() { return new ValuesContainer(); } /** * A view over the set of values of this map. */ public final class ValuesContainer extends AbstractDoubleCollection { private final ObjectDoubleOpenCustomHashMap owner = ObjectDoubleOpenCustomHashMap.this; /** * {@inheritDoc} */ @Override public int size() { return this.owner.size(); } /** * {@inheritDoc} */ @Override public int capacity() { return this.owner.capacity(); } @Override public boolean contains(final double value) { // This is a linear scan over the values, but it's in the contract, so be it. final int[] states = this.owner.allocated; final double[] values = this.owner.values; for (int slot = 0; slot < states.length; slot++) { if ( states[slot] != -1 && (Double.doubleToLongBits(value) == Double.doubleToLongBits(values[slot]))) { return true; } } return false; } @Override public T forEach(final T procedure) { final int[] states = this.owner.allocated; final double[] values = this.owner.values; for (int slot = 0; slot < states.length; slot++) { if ( states[slot] != -1 ) { procedure.apply(values[slot]); } } return procedure; } @Override public T forEach(final T predicate) { final int[] states = this.owner.allocated; final double[] values = this.owner.values; for (int slot = 0; slot < states.length; slot++) { if ( states[slot] != -1 ) { if (!predicate.apply(values[slot])) { break; } } } return predicate; } @Override public ValuesIterator iterator() { // return new ValuesIterator(); return this.valuesIteratorPool.borrow(); } /** * {@inheritDoc} * Indeed removes all the (key,value) pairs matching * (key ? , e) with the same e, from the map. */ @Override public int removeAllOccurrences(final double e) { final int before = this.owner.assigned; final double[] values = this.owner.values; final int[] states = this.owner.allocated; for (int i = 0; i < states.length;) { if (states[i] != -1 ) { if ((Double.doubleToLongBits(e) == Double.doubleToLongBits(values[i]))) { this.owner.assigned--; shiftConflictingKeys(i); // Repeat the check for the same i. continue; } } i++; } return before - this.owner.assigned; } /** * {@inheritDoc} * Indeed removes all the (key,value) pairs matching * the predicate for the values, from the map. */ @Override public int removeAll(final DoublePredicate predicate) { final int before = this.owner.assigned; final double[] values = this.owner.values; final int[] states = this.owner.allocated; for (int i = 0; i < states.length;) { if (states[i] != -1 ) { if (predicate.apply(values[i])) { this.owner.assigned--; shiftConflictingKeys(i); // Repeat the check for the same i. continue; } } i++; } return before - this.owner.assigned; } /** * {@inheritDoc} * Alias for clear() the whole map. */ @Override public void clear() { this.owner.clear(); } /** * internal pool of ValuesIterator */ protected final IteratorPool valuesIteratorPool = new IteratorPool( new ObjectFactory() { @Override public ValuesIterator create() { return new ValuesIterator(); } @Override public void initialize(final ValuesIterator obj) { obj.cursor.index = ObjectDoubleOpenCustomHashMap.this.keys.length; } @Override public void reset(final ValuesIterator obj) { // nothing } }); @Override public double[] toArray(final double[] target) { final int[] states = this.owner.allocated; final double[] values = this.owner.values; int count = 0; for (int i = 0; i < values.length; i++) { if (states[i] != -1 ) { target[count++] = values[i]; } } assert count == this.owner.assigned; return target; } } /** * An iterator over the set of assigned values. */ public final class ValuesIterator extends AbstractIterator { public final DoubleCursor cursor; public ValuesIterator() { this.cursor = new DoubleCursor(); this.cursor.index = -2; } /** * Iterate backwards w.r.t the buffer, to * minimize collision chains when filling another hash container (ex. with putAll()) */ @Override protected DoubleCursor fetch() { int i = this.cursor.index - 1; while (i >= 0 && ObjectDoubleOpenCustomHashMap.this.allocated[i] == -1 ) { i--; } if (i == -1) { return done(); } this.cursor.index = i; this.cursor.value = ObjectDoubleOpenCustomHashMap.this.values[i]; return this.cursor; } } /** * Clone this object. * * The returned clone will use the same HashingStrategy strategy. * It also realizes a trim-to- this.size() in the process. * */ @Override public ObjectDoubleOpenCustomHashMap clone() { /* */ @SuppressWarnings("unchecked") final/* */ ObjectDoubleOpenCustomHashMap cloned = new ObjectDoubleOpenCustomHashMap(this.size(), this.loadFactor, this.hashStrategy); cloned.putAll(this); cloned.defaultValue = this.defaultValue; return cloned; } /** * Convert the contents of this map to a human-friendly string. */ @Override public String toString() { final StringBuilder buffer = new StringBuilder(); buffer.append("["); boolean first = true; for (final ObjectDoubleCursor cursor : this) { if (!first) { buffer.append(", "); } buffer.append(cursor.key); buffer.append("=>"); buffer.append(cursor.value); first = false; } buffer.append("]"); return buffer.toString(); } /** * Creates a hash map from two index-aligned arrays of key-value pairs. */ public static ObjectDoubleOpenCustomHashMap from(final KType[] keys, final double[] values, final ObjectHashingStrategy hashStrategy) { if (keys.length != values.length) { throw new IllegalArgumentException("Arrays of keys and values must have an identical length."); } final ObjectDoubleOpenCustomHashMap map = new ObjectDoubleOpenCustomHashMap(keys.length, hashStrategy); for (int i = 0; i < keys.length; i++) { map.put(keys[i], values[i]); } return map; } /** * Create a hash map from another associative container. */ public static ObjectDoubleOpenCustomHashMap from(final ObjectDoubleAssociativeContainer container, final ObjectHashingStrategy hashStrategy) { return new ObjectDoubleOpenCustomHashMap(container, hashStrategy); } /** * Create a new hash map without providing the full generic signature (constructor * shortcut). */ public static ObjectDoubleOpenCustomHashMap newInstance(final ObjectHashingStrategy hashStrategy) { return new ObjectDoubleOpenCustomHashMap(hashStrategy); } /** * Create a new hash map with initial capacity and load factor control. (constructor * shortcut). */ public static ObjectDoubleOpenCustomHashMap newInstance(final int initialCapacity, final float loadFactor, final ObjectHashingStrategy hashStrategy) { return new ObjectDoubleOpenCustomHashMap(initialCapacity, loadFactor, hashStrategy); } /** * Return the current {@link ObjectHashingStrategy} in use. */ public ObjectHashingStrategy strategy() { return this.hashStrategy; } /** * Returns the "default value" value used * in containers methods returning "default value" * @return */ public double getDefaultValue() { return this.defaultValue; } /** * Set the "default value" value to be used * in containers methods returning "default value" * @return */ public void setDefaultValue(final double defaultValue) { this.defaultValue = defaultValue; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy