com.carrotsearch.hppcrt.maps.ObjectDoubleHashMap Maven / Gradle / Ivy
Show all versions of hppcrt Show documentation
package com.carrotsearch.hppcrt.maps;
import com.carrotsearch.hppcrt.*;
import com.carrotsearch.hppcrt.cursors.*;
import com.carrotsearch.hppcrt.predicates.*;
import com.carrotsearch.hppcrt.procedures.*;
import com.carrotsearch.hppcrt.hash.*;
// If RH is defined, RobinHood Hashing is in effect :
/**
* A hash map of Object
to double
, implemented using open
* addressing with linear probing for collision resolution.
*
* In addition, the hashing strategy can be changed
* by overriding ({@link #equalKeys(Object, Object)} and {@link #hashKey(Object)}) together,
* which then replaces the usual ({@link #equals(Object)} and {@link #hashCode()}) from the keys themselves.
* This is useful to define the equivalence of keys when the user has no control over the keys implementation.
*
*
* The internal buffers of this implementation ({@link #keys}, {@link #values}),
* 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.
*
*
* This implementation supports null
keys.
*
* 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.
*
*
*
*
*
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 = "2017-07-11T19:16:27+0200",
value = "KTypeVTypeHashMap.java")
public class ObjectDoubleHashMap
implements ObjectDoubleMap, Cloneable
{
protected double defaultValue = (0.0);
/**
* Hash-indexed array holding all keys.
*
* Direct map iteration: iterate {keys[i], values[i]} for i in [0; keys.length[ where keys[i] != 0/null, then also
* {0/null, {@link #allocatedDefaultKeyValue} } is in the map if {@link #allocatedDefaultKey} = true.
*
*/
public
Object[]
keys;
/**
* Hash-indexed array holding all values associated to the keys.
* stored in {@link #keys}.
*/
public double []
values;
/**
* * Caches the hash value = hash(keys[i]) & mask, if keys[i] != 0/null,
* for every index i.
* * @see #assigned
*/
protected int[] hash_cache;
/**
* True if key = 0/null is in the map.
*/
public boolean allocatedDefaultKey = false;
/**
* if allocatedDefaultKey = true, contains the associated V to the key = 0/null
*/
public double allocatedDefaultKeyValue;
/**
* Cached number of assigned slots in {@link #keys}.
*/
protected int assigned;
/**
* The load factor for this map (fraction of allocated slots
* before the buffers must be rehashed or reallocated).
*/
protected final double loadFactor;
/**
* Resize buffers when {@link #keys} hits this value.
*/
private int resizeAt;
/**
* Per-instance size perturbation
* introduced in rehashing to create a unique key distribution.
*/
private final int perturbation = Containers.randomSeed32();
/**
* Override this method, together with {@link #equalKeys(Object, Object)}
* to customize the hashing strategy. Note that this method is guaranteed
* to be called with a non-null key argument.
* By default, this method calls key.{@link #hashCode()}.
* @param key Object to be hashed.
* @return the hashed value of key, following the same semantic
* as {@link #hashCode()};
* @see #hashCode()
* @see #equalKeys(Object, Object)
*/
protected int hashKey(final KType key) {
//default maps on Object.hashCode()
return key.hashCode();
}
/**
* Override this method together with {@link #hashKey(Object)}
* to customize the hashing strategy. Note that this method is guaranteed
* to be called with both non-null arguments.
* By default, this method calls a.{@link #equals(b)}.
* @param a not-null Object to be compared
* @param b not-null Object to be compared
* @return true if a and b are considered equal, following the same
* semantic as {@link #equals(Object)}.
* @see #equals(Object)
* @see #hashKey(Object)
*/
protected boolean equalKeys(final KType a, final KType b) {
//default maps on Object.equals()
return ((a).equals((b)));
}
/**
* Default constructor: Creates a hash map with the default capacity of {@link Containers#DEFAULT_EXPECTED_ELEMENTS},
* load factor of {@link HashContainers#DEFAULT_LOAD_FACTOR}.
*
* See class notes about hash distribution importance.
*/
public ObjectDoubleHashMap() {
this(Containers.DEFAULT_EXPECTED_ELEMENTS);
}
/**
* Creates a hash map with the given initial capacity, default load factor of
* {@link HashContainers#DEFAULT_LOAD_FACTOR}.
*
* See class notes about hash distribution importance.
*
* @param initialCapacity Initial capacity (greater than zero and automatically
* rounded to the next power of two).
*/
public ObjectDoubleHashMap(final int initialCapacity) {
this(initialCapacity, HashContainers.DEFAULT_LOAD_FACTOR);
}
/**
* Creates a hash map with the given initial capacity,
* load factor.
*
* @param loadFactor The load factor (greater than zero and smaller than 1).
*/
public ObjectDoubleHashMap(final int initialCapacity, final double loadFactor) {
this.loadFactor = loadFactor;
//take into account of the load factor to guarantee no reallocations before reaching initialCapacity.
allocateBuffers(HashContainers.minBufferSize(initialCapacity, loadFactor));
}
/**
* Create a hash map from all key-value pairs of another container.
*/
public ObjectDoubleHashMap(final ObjectDoubleAssociativeContainer container) {
this(container.size());
putAll(container);
}
/**
* {@inheritDoc}
*/
@Override
public double put(KType key, double value) {
if (((key) == null)) {
if (this.allocatedDefaultKey) {
final double previousValue = this.allocatedDefaultKeyValue;
this.allocatedDefaultKeyValue = value;
return previousValue;
}
this.allocatedDefaultKeyValue = value;
this.allocatedDefaultKey = true;
return this.defaultValue;
}
final int mask = this.keys.length - 1;
final KType[] keys = ((KType[])(this.keys));
int slot = (BitMixer.mix(hashKey((key)) , this.perturbation)) & mask;
KType existing;
final double[] values = ((this.values));
final int[] cached = this.hash_cache;
KType tmpKey;
double tmpValue;
int tmpAllocated;
int initial_slot = slot;
int dist = 0;
int existing_distance = 0;
while (!((existing = keys[slot]) == null)) {
existing_distance = probe_distance(slot, cached);
//When first entering the while loop, then key == original key to search.
//So either:
//1) key is immediately found and the routine bail out,
//or
//2) If the Robin-hood criteria of distance is not met, we search the next slot, (usual linear probing)
//or
//3) else the criteria of distance is met, then (key,value) is swapped with the ones in
//slot position which becomes the new (key,value) to consider. This is OK because keys are swapped only if dist > existing_distance,
//i.e only if the key to add is NOT in the map, see containsKey(). So we steal the rich (a previously entered key, favored because having being inserted
//in a less crowed array) to give to the poor, the now inserted key. Then, we start searching again in the next slot.
// Robin-hood shortcut: if key exists, it can only be found in dist <= existing_distance range.
//indeed we should expect to never see an existing element with a shorter probe count (existing_distance)
//than our current count (dist): if that had happened, there would’ve been a swap during insertion, see below.
//also see containsKey(), get() and remove() for the same trick.
if ( dist <= existing_distance && (equalKeys((key), (existing)))) {
final double oldValue = ((this.values[slot]));
this.values[slot] = value;
return oldValue;
}
//re-shuffle keys to minimize variance
//we actually enter here only if the key to add is NOT in the map.
if (dist > existing_distance) {
//swap current (key, value, initial_slot) with slot places
tmpKey = keys[slot];
keys[slot] = key;
key = tmpKey;
tmpAllocated = cached[slot];
cached[slot] = initial_slot;
initial_slot = tmpAllocated;
tmpValue = values[slot];
values[slot] = value;
value = tmpValue;
dist = existing_distance;
}
slot = (slot + 1) & mask;
dist++;
} //end while
// 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++;
cached[slot] = initial_slot;
keys[slot] = key;
values[slot] = value;
}
return this.defaultValue;
}
/**
* {@inheritDoc}
*/
@Override
public int putAll(final ObjectDoubleAssociativeContainer extends KType> container) {
return putAll((Iterable extends ObjectDoubleCursor extends KType>>) container);
}
/**
* {@inheritDoc}
*/
@Override
public int putAll(final Iterable extends ObjectDoubleCursor extends KType>> iterable) {
final int count = this.size();
for (final ObjectDoubleCursor extends KType> c : iterable) {
put(c.key, c.value);
}
return this.size() - count;
}
/**
* {@inheritDoc}
*/
@Override
public boolean putIfAbsent(final KType key, final double value) {
if (!containsKey(key)) {
put(key, value);
return true;
}
return false;
}
/**
* If key
exists, putValue
is inserted into the map,
* otherwise any existing value is incremented by additionValue
.
*
* @param key
* The key of the value to adjust.
* @param putValue
* The value to put if key
does not exist.
* @param incrementValue
* The value to add to the existing value if key
exists.
* @return Returns the current value associated with key
(after
* changes).
*/
@SuppressWarnings("cast")
@Override
public double putOrAdd(final KType key, double putValue, final double incrementValue) {
if (containsKey(key)) {
putValue = get(key);
putValue = (double) (((putValue) + (incrementValue)));
}
put(key, putValue);
return putValue;
}
/**
* Adds incrementValue
to any existing value for the given key
* or inserts incrementValue
if key
did not previously exist.
*
* @param key The key of the value to adjust.
* @param incrementValue 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(final KType key, final double incrementValue)
{
return putOrAdd(key, incrementValue, incrementValue);
}
/**
* 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;
//default sentinel value is never in the keys[] array, so never trigger reallocs
assert !((pendingKey) == null);
// 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 = ((KType[])(this.keys));
final double[] oldValues = ((this.values));
allocateBuffers(HashContainers.nextBufferSize(this.keys.length, this.assigned, this.loadFactor));
// We have succeeded at allocating new data so insert the pending key/value at
// the free slot in the old arrays before rehashing.
this.assigned++;
oldKeys[freeSlot] = pendingKey;
oldValues[freeSlot] = pendingValue;
//for inserts
final int mask = this.keys.length - 1;
KType key = (null);
double value = (0.0);
int slot = -1;
final KType[] keys = ((KType[])(this.keys));
final double[] values = ((this.values));
final int[] cached = this.hash_cache;
KType tmpKey = (null);
double tmpValue = (0.0);
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 !
final int perturb = this.perturbation;
for (int i = oldKeys.length; --i >= 0;) {
//only consider non-empty slots, of course
if (!((key = oldKeys[i]) == null)) {
value = oldValues[i];
slot = (BitMixer.mix(hashKey((key)) , (perturb))) & mask;
initial_slot = slot;
dist = 0;
//similar to put(), except all inserted keys are known to be unique.
while ((!(((keys)[(slot)]) == null))) {
//re-shuffle keys to minimize variance
existing_distance = probe_distance(slot, cached);
if (dist > existing_distance) {
//swap current (key, value, initial_slot) with slot places
tmpKey = keys[slot];
keys[slot] = key;
key = tmpKey;
tmpAllocated = cached[slot];
cached[slot] = initial_slot;
initial_slot = tmpAllocated;
tmpValue = values[slot];
values[slot] = value;
value = tmpValue;
dist = existing_distance;
}
slot = (slot + 1) & mask;
dist++;
} //end while
cached[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).
*/
@SuppressWarnings("boxing")
private void allocateBuffers(final int capacity) {
try {
final KType[] keys = ((KType[])new Object[(capacity)]);
final double[] values = (new double[(capacity)]);
final int[] cached = new int[capacity];
this.keys = keys;
this.values = values;
this.hash_cache = cached;
//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 = HashContainers.expandAtCount(capacity, this.loadFactor);
} catch (final OutOfMemoryError e) {
throw new BufferAllocationException(
"Not enough memory to allocate buffers to grow from %d -> %d elements",
e,
(this.keys == null) ? 0 : this.keys.length,
capacity);
}
}
/**
* {@inheritDoc}
*/
@Override
public double remove(final KType key) {
if (((key) == null)) {
if (this.allocatedDefaultKey) {
final double previousValue = this.allocatedDefaultKeyValue;
this.allocatedDefaultKey = false;
return previousValue;
}
return this.defaultValue;
}
final int mask = this.keys.length - 1;
final KType[] keys = ((KType[])(this.keys));
int slot = (BitMixer.mix(hashKey((key)) , this.perturbation)) & mask;
KType existing;
int dist = 0;
final int[] cached = this.hash_cache;
while (!((existing = keys[slot]) == null)
&& dist <= probe_distance(slot, cached) ) {
if ((equalKeys((key), (existing)))) {
final double value = ((this.values[slot]));
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
.
*/
private void shiftConflictingKeys(int gapSlot) {
final int mask = this.keys.length - 1;
final KType[] keys = ((KType[])(this.keys));
final double[] values = ((this.values));
final int[] cached = this.hash_cache;
// Perform shifts of conflicting keys to fill in the gap.
int distance = 0;
while (true) {
final int slot = (gapSlot + (++distance)) & mask;
final KType existing = keys[slot];
final double existingValue = values[slot];
if (((existing) == null)) {
break;
}
//use the cached value, no need to recompute
final int idealSlotModMask = cached[slot];
//original HPPC code: shift = (slot - idealSlot) & mask;
//equivalent to shift = (slot & mask - idealSlot & mask) & mask;
//since slot and idealSlotModMask are already folded, we have :
final int shift = (slot - idealSlotModMask) & mask;
if (shift >= distance) {
// Entry at this position was originally at or before the gap slot.
// Move the conflict-shifted entry to the gap's position and repeat the procedure
// for any entries to the right of the current position, treating it
// as the new gap.
keys[gapSlot] = existing;
values[gapSlot] = existingValue;
cached[gapSlot] = idealSlotModMask;
gapSlot = slot;
distance = 0;
}
} //end while
// Mark the last found gap slot without a conflict as empty.
keys[gapSlot] = (null);
/* */
this.assigned--;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public int removeAll(final ObjectContainer super KType> other) {
final int before = this.size();
//1) other is a ObjectLookupContainer, so with fast lookup guarantees
//and is bigger than this, so take advantage of both and iterate over this
//and test other elements by their contains().
if (other.size() >= before && other instanceof ObjectLookupContainer>) {
if (this.allocatedDefaultKey) {
if (other.contains((null))) {
this.allocatedDefaultKey = false;
}
}
final KType[] keys = ((KType[])(this.keys));
for (int i = 0; i < keys.length;) {
KType existing;
if (!((existing = keys[i]) == null) && other.contains(existing)) {
shiftConflictingKeys(i);
// Shift, do not increment slot.
} else {
i++;
}
}
} else {
//2) Do not use contains() from container, which may lead to O(n**2) execution times,
//so it iterate linearly and call remove() from map which is O(1).
for (final ObjectCursor super KType> c : other) {
remove(((KType)(c.value)));
}
}
return before - this.size();
}
/**
* {@inheritDoc}
*/
@Override
public int removeAll(final ObjectPredicate super KType> predicate) {
final int before = this.size();
if (this.allocatedDefaultKey) {
if (predicate.apply((null))) {
this.allocatedDefaultKey = false;
}
}
final KType[] keys = ((KType[])(this.keys));
for (int i = 0; i < keys.length;) {
KType existing;
if (!((existing = keys[i]) == null) && predicate.apply(existing)) {
shiftConflictingKeys(i);
// Shift, do not increment slot.
} else {
i++;
}
}
return before - this.size();
}
/**
* {@inheritDoc}
*/
@Override
public int removeAll(final ObjectDoublePredicate super KType> predicate) {
final int before = this.size();
if (this.allocatedDefaultKey) {
if (predicate.apply((null), this.allocatedDefaultKeyValue)) {
this.allocatedDefaultKey = false;
}
}
final KType[] keys = ((KType[])(this.keys));
final double[] values = ((this.values));
for (int i = 0; i < keys.length;) {
KType existing;
if (!((existing = keys[i]) == null) && predicate.apply(existing, values[i])) {
shiftConflictingKeys(i);
// Shift, do not increment slot.
} else {
i++;
}
}
return before - this.size();
}
/**
* {@inheritDoc}
*/
@Override
public double get(final KType key) {
if (((key) == null)) {
if (this.allocatedDefaultKey) {
return this.allocatedDefaultKeyValue;
}
return this.defaultValue;
}
final int mask = this.keys.length - 1;
final KType[] keys = ((KType[])(this.keys));
int slot = (BitMixer.mix(hashKey((key)) , this.perturbation)) & mask;
KType existing;
int dist = 0;
final int[] cached = this.hash_cache;
while (!((existing = keys[slot]) == null)
&& dist <= probe_distance(slot, cached) ) {
if ((equalKeys((key), (existing)))) {
return ((this.values[slot]));
}
slot = (slot + 1) & mask;
dist++;
} //end while true
return this.defaultValue;
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsKey(final KType key) {
if (((key) == null)) {
return this.allocatedDefaultKey;
}
final int mask = this.keys.length - 1;
final KType[] keys = ((KType[])(this.keys));
int slot = (BitMixer.mix(hashKey((key)) , this.perturbation)) & mask;
KType existing;
int dist = 0;
final int[] cached = this.hash_cache;
while (!((existing = keys[slot]) == null)
&& dist <= probe_distance(slot, cached) ) {
if ((equalKeys((key), (existing)))) {
return true;
}
slot = (slot + 1) & mask;
dist++;
} //end while true
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void clear() {
this.assigned = 0;
// States are always cleared.
this.allocatedDefaultKey = false;
//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 + (this.allocatedDefaultKey ? 1 : 0);
}
/**
* {@inheritDoc}
*/
@Override
public int capacity() {
return this.resizeAt;
}
/**
* {@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;
if (this.allocatedDefaultKey) {
h += BitMixer.mix(this.allocatedDefaultKeyValue);
}
final KType[] keys = ((KType[])(this.keys));
final double[] values = ((this.values));
for (int i = keys.length; --i >= 0;) {
KType existing;
if (!((existing = keys[i]) == null)) {
h += BitMixer.mix(existing) ^ BitMixer.mix(values[i]);
}
}
return h;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(final Object obj) {
if (obj != null) {
if (obj == this) {
return true;
}
//must be of the same class, subclasses are not comparable
if (obj.getClass() != this.getClass()) {
return false;
}
/* */
@SuppressWarnings("unchecked")
final/* */
ObjectDoubleHashMap other = (ObjectDoubleHashMap) obj;
//must be of the same size
if (other.size() != this.size()) {
return false;
}
final EntryIterator it = this.iterator();
while (it.hasNext()) {
final ObjectDoubleCursor c = it.next();
if (!other.containsKey(c.key)) {
//recycle
it.release();
return false;
}
final double otherValue = other.get(c.key);
if (!(Double.doubleToLongBits((c.value)) == Double.doubleToLongBits((otherValue)))) {
//recycle
it.release();
return false;
}
} //end while
return true;
}
return false;
}
/**
* An iterator implementation for {@link #iterator}.
* Holds a ObjectDoubleCursor returning
* (key, value, index) = (Object key, double value, index the position in keys {@link ObjectDoubleHashMap#keys}, or keys.length for key = 0/null)
*/
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() {
if (this.cursor.index == ObjectDoubleHashMap.this.keys.length + 1) {
if (ObjectDoubleHashMap.this.allocatedDefaultKey) {
this.cursor.index = ObjectDoubleHashMap.this.keys.length;
this.cursor.key = (null);
this.cursor.value = ObjectDoubleHashMap.this.allocatedDefaultKeyValue;
return this.cursor;
}
//no value associated with the default key, continue iteration...
this.cursor.index = ObjectDoubleHashMap.this.keys.length;
}
int i = this.cursor.index - 1;
while (i >= 0 && !(!(((((KType[])(ObjectDoubleHashMap.this.keys)))[(i)]) == null))) {
i--;
}
if (i == -1) {
return done();
}
this.cursor.index = i;
this.cursor.key = ((KType)(ObjectDoubleHashMap.this.keys[i]));
this.cursor.value = ((ObjectDoubleHashMap.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 = ObjectDoubleHashMap.this.keys.length + 1;
}
@Override
public void reset(final EntryIterator obj) {
obj.cursor.key = null;
}
});
/**
* {@inheritDoc}
*/
@Override
public EntryIterator iterator() {
//return new EntryIterator();
return this.entryIteratorPool.borrow();
}
/**
* {@inheritDoc}
*/
@Override
public > T forEach(final T procedure) {
if (this.allocatedDefaultKey) {
procedure.apply((null), this.allocatedDefaultKeyValue);
}
final KType[] keys = ((KType[])(this.keys));
final double[] values = ((this.values));
//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 = keys.length - 1; i >= 0; i--) {
KType existing;
if (!((existing = keys[i]) == null)) {
procedure.apply(existing, values[i]);
}
}
return procedure;
}
/**
* {@inheritDoc}
*/
@Override
public > T forEach(final T predicate) {
if (this.allocatedDefaultKey) {
if (!predicate.apply((null), this.allocatedDefaultKeyValue)) {
return predicate;
}
}
final KType[] keys = ((KType[])(this.keys));
final double[] values = ((this.values));
//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 = keys.length - 1; i >= 0; i--) {
KType existing;
if (!((existing = keys[i]) == null)) {
if (!predicate.apply(existing, values[i])) {
break;
}
}
} //end for
return predicate;
}
/**
* {@inheritDoc}
* @return a new KeysCollection view of the keys of this map.
*/
@Override
public KeysCollection keys() {
return new KeysCollection();
}
/**
* A view of the keys inside this map.
*/
public final class KeysCollection extends AbstractObjectCollection implements ObjectLookupContainer
{
private final ObjectDoubleHashMap owner = ObjectDoubleHashMap.this;
@Override
public boolean contains(final KType e) {
return containsKey(e);
}
@Override
public > T forEach(final T procedure) {
if (this.owner.allocatedDefaultKey) {
procedure.apply((null));
}
final KType[] keys = ((KType[])(this.owner.keys));
//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 = keys.length - 1; i >= 0; i--) {
KType existing;
if (!((existing = keys[i]) == null)) {
procedure.apply(existing);
}
}
return procedure;
}
@Override
public > T forEach(final T predicate) {
if (this.owner.allocatedDefaultKey) {
if (!predicate.apply((null))) {
return predicate;
}
}
final KType[] keys = ((KType[])(this.owner.keys));
//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 = keys.length - 1; i >= 0; i--) {
KType existing;
if (!((existing = keys[i]) == null)) {
if (!predicate.apply(existing)) {
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 super KType> predicate) {
return this.owner.removeAll(predicate);
}
@Override
public int removeAll(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 = ObjectDoubleHashMap.this.keys.length + 1;
}
@Override
public void reset(final KeysIterator obj) {
obj.cursor.value = null;
}
});
@Override
public KType[] toArray(final KType[] target) {
int count = 0;
if (this.owner.allocatedDefaultKey) {
target[count++] = (null);
}
final KType[] keys = ((KType[])(this.owner.keys));
for (int i = 0; i < keys.length; i++) {
KType existing;
if (!((existing = keys[i]) == null)) {
target[count++] = existing;
}
}
assert count == this.owner.size();
return target;
}
};
/**
* An iterator over the set of keys.
* Holds a ObjectCursor returning (value, index) = (Object key, index the position in buffer {@link ObjectDoubleHashMap#keys}, or keys.length for key = 0/null.)
*/
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() {
if (this.cursor.index == ObjectDoubleHashMap.this.keys.length + 1) {
if (ObjectDoubleHashMap.this.allocatedDefaultKey) {
this.cursor.index = ObjectDoubleHashMap.this.keys.length;
this.cursor.value = (null);
return this.cursor;
}
//no value associated with the default key, continue iteration...
this.cursor.index = ObjectDoubleHashMap.this.keys.length;
}
int i = this.cursor.index - 1;
while (i >= 0 && !(!(((((KType[])(ObjectDoubleHashMap.this.keys)))[(i)]) == null))) {
i--;
}
if (i == -1) {
return done();
}
this.cursor.index = i;
this.cursor.value = ((KType)(ObjectDoubleHashMap.this.keys[i]));
return this.cursor;
}
}
/**
* {@inheritDoc}
* @return a new ValuesCollection view of the values of this map.
*/
@Override
public ValuesCollection values() {
return new ValuesCollection();
}
/**
* A view over the set of values of this map.
*/
public final class ValuesCollection extends AbstractDoubleCollection
{
private final ObjectDoubleHashMap owner = ObjectDoubleHashMap.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) {
if (this.owner.allocatedDefaultKey && (Double.doubleToLongBits((value)) == Double.doubleToLongBits((this.owner.allocatedDefaultKeyValue)))) {
return true;
}
// This is a linear scan over the values, but it's in the contract, so be it.
final KType[] keys = ((KType[])(this.owner.keys));
final double[] values = ((this.owner.values));
for (int slot = 0; slot < keys.length; slot++) {
if ((!(((keys)[(slot)]) == null)) && (Double.doubleToLongBits((value)) == Double.doubleToLongBits((values[slot])))) {
return true;
}
}
return false;
}
@Override
public T forEach(final T procedure) {
if (this.owner.allocatedDefaultKey) {
procedure.apply(this.owner.allocatedDefaultKeyValue);
}
final KType[] keys = ((KType[])(this.owner.keys));
final double[] values = ((this.owner.values));
for (int slot = 0; slot < keys.length; slot++) {
if ((!(((keys)[(slot)]) == null))) {
procedure.apply(values[slot]);
}
}
return procedure;
}
@Override
public T forEach(final T predicate) {
if (this.owner.allocatedDefaultKey) {
if (!predicate.apply(this.owner.allocatedDefaultKeyValue)) {
return predicate;
}
}
final KType[] keys = ((KType[])(this.owner.keys));
final double[] values = ((this.owner.values));
for (int slot = 0; slot < keys.length; slot++) {
if ((!(((keys)[(slot)]) == null))) {
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 removeAll(final double e) {
final int before = this.owner.size();
if (this.owner.allocatedDefaultKey) {
if ((Double.doubleToLongBits((e)) == Double.doubleToLongBits((this.owner.allocatedDefaultKeyValue)))) {
this.owner.allocatedDefaultKey = false;
}
}
final KType[] keys = ((KType[])(this.owner.keys));
final double[] values = ((this.owner.values));
for (int slot = 0; slot < keys.length;) {
if ((!(((keys)[(slot)]) == null)) && (Double.doubleToLongBits((e)) == Double.doubleToLongBits((values[slot])))) {
shiftConflictingKeys(slot);
// Shift, do not increment slot.
} else {
slot++;
}
}
return before - this.owner.size();
}
/**
* {@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.size();
if (this.owner.allocatedDefaultKey) {
if (predicate.apply(this.owner.allocatedDefaultKeyValue)) {
this.owner.allocatedDefaultKey = false;
}
}
final KType[] keys = ((KType[])(this.owner.keys));
final double[] values = ((this.owner.values));
for (int slot = 0; slot < keys.length;) {
if ((!(((keys)[(slot)]) == null)) && predicate.apply(values[slot])) {
shiftConflictingKeys(slot);
// Shift, do not increment slot.
} else {
slot++;
}
}
return before - this.owner.size();
}
/**
* {@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 = ObjectDoubleHashMap.this.keys.length + 1;
}
@Override
public void reset(final ValuesIterator obj) {
}
});
@Override
public double[] toArray(final double[] target) {
int count = 0;
if (this.owner.allocatedDefaultKey) {
target[count++] = this.owner.allocatedDefaultKeyValue;
}
final KType[] keys = ((KType[])(this.owner.keys));
final double[] values = ((this.owner.values));
for (int i = 0; i < values.length; i++) {
if ((!(((keys)[(i)]) == null))) {
target[count++] = values[i];
}
}
assert count == this.owner.size();
return target;
}
}
/**
* An iterator over the set of values.
* Holds a ObjectCursor returning (value, index) = (double value, index the position in buffer {@link ObjectDoubleHashMap#values},
* or values.length for value = {@link ObjectDoubleHashMap#allocatedDefaultKeyValue}).
*/
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() {
if (this.cursor.index == ObjectDoubleHashMap.this.values.length + 1) {
if (ObjectDoubleHashMap.this.allocatedDefaultKey) {
this.cursor.index = ObjectDoubleHashMap.this.values.length;
this.cursor.value = ObjectDoubleHashMap.this.allocatedDefaultKeyValue;
return this.cursor;
}
//no value associated with the default key, continue iteration...
this.cursor.index = ObjectDoubleHashMap.this.keys.length;
}
int i = this.cursor.index - 1;
while (i >= 0 && !(!(((((KType[])(ObjectDoubleHashMap.this.keys)))[(i)]) == null))) {
i--;
}
if (i == -1) {
return done();
}
this.cursor.index = i;
this.cursor.value = ((ObjectDoubleHashMap.this.values[i]));
return this.cursor;
}
}
/**
* {@inheritDoc}
*/
@Override
public ObjectDoubleHashMap clone() {
//clone to size() to prevent some cases of exponential sizes,
final ObjectDoubleHashMap cloned = new ObjectDoubleHashMap(this.size(), this.loadFactor);
//We must NOT clone because of independent perturbations seeds
cloned.putAll(this);
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. Default load factor is used.
*/
public static ObjectDoubleHashMap from(final KType[] keys, final double[] values) {
if (keys.length != values.length) {
throw new IllegalArgumentException("Arrays of keys and values must have an identical length.");
}
final ObjectDoubleHashMap map = new ObjectDoubleHashMap(keys.length);
for (int i = 0; i < keys.length; i++) {
map.put(keys[i], values[i]);
}
return map;
}
/**
* Create a hash map from another associative container. (constructor shortcut) Default load factor is used.
*/
public static ObjectDoubleHashMap from(
final ObjectDoubleAssociativeContainer container) {
return new ObjectDoubleHashMap(container);
}
/**
* Create a new hash map without providing the full generic signature
* (constructor shortcut).
*/
public static ObjectDoubleHashMap newInstance() {
return new ObjectDoubleHashMap();
}
/**
* Create a new hash map with initial capacity and load factor control.
* (constructor shortcut).
*/
public static ObjectDoubleHashMap newInstance(final int initialCapacity,
final double loadFactor) {
return new ObjectDoubleHashMap(initialCapacity, loadFactor);
}
/**
* Returns the "default value" value used in containers methods returning
* "default value"
*/
@Override
public double getDefaultValue() {
return this.defaultValue;
}
/**
* Set the "default value" value to be used in containers methods returning
* "default value"
*/
@Override
public void setDefaultValue(final double defaultValue) {
this.defaultValue = defaultValue;
}
//Test for existence in template
private int probe_distance(final int slot, final int[] cache) {
final int rh = cache[slot];
if (slot < rh) {
//wrap around
return slot - rh + cache.length;
}
return slot - rh;
}
}