diff --git a/CHANGES.txt b/CHANGES.txt index f14e3d1b9..3c3e400eb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +[0.7.2] +** API-breaking changes +HPPCRT-46: Remove CurstomHash and replace by equals/hashCode overrides in normal hash containers. + [0.7.1] ** Bug fixes HPPCRT-45: API with intervals arguments is inconsistent with JDK conventions. diff --git a/README.txt b/README.txt index a3678c4fe..32a4fa2a7 100644 --- a/README.txt +++ b/README.txt @@ -45,7 +45,7 @@ Stable version is available on Maven: com.github.vsonnier hppcrt - 0.7.1 + 0.7.2 ```` diff --git a/hppcrt-benchmarks/pom.xml b/hppcrt-benchmarks/pom.xml index f26e56bba..42a979c8c 100644 --- a/hppcrt-benchmarks/pom.xml +++ b/hppcrt-benchmarks/pom.xml @@ -5,13 +5,13 @@ com.github.vsonnier hppcrt-parent - 0.7.2-SNAPSHOT + 0.7.2 ../pom.xml hppcrt-benchmarks - 0.7.2-SNAPSHOT + 0.7.2 jar HPPC-RT Benchmarks @@ -24,14 +24,14 @@ 2.6 UTF-8 - 1.11 + 1.11.1 1.7 benchmarks 0.7.1 - 7.0.6 + 7.0.7 6.2.0 - 0.6.7 + 0.6.8 6.0.0 diff --git a/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/implementations/HashMapImplementations.java b/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/implementations/HashMapImplementations.java index 1d8b581a9..34e91477d 100644 --- a/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/implementations/HashMapImplementations.java +++ b/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/implementations/HashMapImplementations.java @@ -79,20 +79,6 @@ public boolean isHashQualityApplicable() { } }, - HPPCRT_OBJ_INT_STRATEGY - { - @Override - public MapImplementation getInstance(final int size, final float loadFactor) - { - return new HppcrtObjectIntCustomMap(size, loadFactor); - } - - @Override - public boolean isHashQualityApplicable() { - - return true; - } - }, HPPC_OBJ_INT { @Override diff --git a/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/implementations/HppcrtObjectIntCustomMap.java b/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/implementations/HppcrtObjectIntCustomMap.java deleted file mode 100644 index c40220e20..000000000 --- a/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/implementations/HppcrtObjectIntCustomMap.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.carrotsearch.hppcrt.implementations; - -import java.util.Random; - -import org.openjdk.jmh.infra.Blackhole; - -import com.carrotsearch.hppcrt.XorShift128P; -import com.carrotsearch.hppcrt.maps.ObjectIntCustomHashMap; -import com.carrotsearch.hppcrt.strategies.ObjectHashingStrategy; - -public class HppcrtObjectIntCustomMap extends MapImplementation> -{ - - private ComparableInt[] insertKeys; - private ComparableInt[] containsKeys; - private ComparableInt[] removedKeys; - private int[] insertValues; - - protected HppcrtObjectIntCustomMap(final int size, final float loadFactor) - { - super(new ObjectIntCustomHashMap(size, loadFactor, - //A good behaved startegy that compensates bad hashCode() implementation. - new ObjectHashingStrategy() { - - @Override - public int computeHashCode(final MapImplementation.ComparableInt object) { - - //eat some CPU to simulate method cost - Blackhole.consumeCPU(MapImplementation.METHOD_CALL_CPU_COST); - - return object.value; - } - - @Override - public boolean equals(final MapImplementation.ComparableInt o1, final MapImplementation.ComparableInt o2) { - - //eat some CPU to simulate method cost - Blackhole.consumeCPU(MapImplementation.METHOD_CALL_CPU_COST); - - return o1.value == o2.value; - } - })); - } - - /** - * Setup - */ - @Override - public void setup(final int[] keysToInsert, final MapImplementation.HASH_QUALITY hashQ, final int[] keysForContainsQuery, final int[] keysForRemovalQuery) { - - final Random prng = new XorShift128P(0x122335577L); - - this.insertKeys = new ComparableInt[keysToInsert.length]; - - this.containsKeys = new ComparableInt[keysForContainsQuery.length]; - this.removedKeys = new ComparableInt[keysForRemovalQuery.length]; - - this.insertValues = new int[keysToInsert.length]; - - //Auto box into Integers, they must have the same length anyway. - for (int i = 0; i < keysToInsert.length; i++) { - - this.insertKeys[i] = new ComparableInt(keysToInsert[i], hashQ); - - this.insertValues[i] = prng.nextInt(); - } - - //Auto box into Integers - for (int i = 0; i < keysForContainsQuery.length; i++) { - - this.containsKeys[i] = new ComparableInt(keysForContainsQuery[i], hashQ); - } - - //Auto box into Integers - for (int i = 0; i < keysForRemovalQuery.length; i++) { - - this.removedKeys[i] = new ComparableInt(keysForRemovalQuery[i], hashQ); - } - } - - @Override - public void clear() { - this.instance.clear(); - } - - @Override - public int size() { - - return this.instance.size(); - } - - @Override - public int benchPutAll() { - - final ObjectIntCustomHashMap instance = this.instance; - - final int[] values = this.insertValues; - - int count = 0; - - final ComparableInt[] keys = this.insertKeys; - - for (int i = 0; i < keys.length; i++) { - - count += instance.put(keys[i], values[i]); - } - - return count; - } - - @Override - public int benchContainKeys() - { - final ObjectIntCustomHashMap instance = this.instance; - - int count = 0; - - final ComparableInt[] keys = this.containsKeys; - - for (int i = 0; i < keys.length; i++) { - - count += instance.containsKey(keys[i]) ? 1 : 0; - } - - return count; - } - - @Override - public int benchRemoveKeys() { - - final ObjectIntCustomHashMap instance = this.instance; - - int count = 0; - - final ComparableInt[] keys = this.removedKeys; - - for (int i = 0; i < keys.length; i++) { - - count += instance.remove(keys[i]); - } - - return count; - } - - @SuppressWarnings("unchecked") - @Override - public void setCopyOfInstance(final MapImplementation toCloneFrom) { - - this.instance = ((ObjectIntCustomHashMap) toCloneFrom.instance).clone(); - - } -} \ No newline at end of file diff --git a/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/misc/HppcMapSyntheticBench.java b/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/misc/HppcMapSyntheticBench.java index 16ea3182a..7bd2ba810 100644 --- a/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/misc/HppcMapSyntheticBench.java +++ b/hppcrt-benchmarks/src/main/java/com/carrotsearch/hppcrt/misc/HppcMapSyntheticBench.java @@ -15,11 +15,9 @@ import com.carrotsearch.hppcrt.lists.LongArrayList; import com.carrotsearch.hppcrt.lists.ObjectArrayList; import com.carrotsearch.hppcrt.maps.IntLongHashMap; -import com.carrotsearch.hppcrt.maps.ObjectLongCustomHashMap; import com.carrotsearch.hppcrt.maps.ObjectLongHashMap; import com.carrotsearch.hppcrt.maps.ObjectLongIdentityHashMap; import com.carrotsearch.hppcrt.procedures.LongProcedure; -import com.carrotsearch.hppcrt.strategies.ObjectHashingStrategy; public final class HppcMapSyntheticBench { @@ -137,25 +135,7 @@ public boolean equals(final Object obj) } } - /** - * Testing for strategies - */ - private final ObjectHashingStrategy INTHOLDER_TRIVIAL_STRATEGY = new ObjectHashingStrategy() { - - @Override - public int computeHashCode(final ComparableInt o) - { - return o.value; - } - - @Override - public boolean equals(final ComparableInt o1, final ComparableInt o2) - { - return o1.value == o2.value; - } - }; - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Constructor */ @@ -732,14 +712,6 @@ ObjectLongHashMap. newInstance(HppcMapSyntheticBench.COUNT, HashC getKind, HASH_QUALITY.BAD, dis); System.gc(); } - - // use specialized strategy to overcome the bad hash behaviour above. - runMapIntegerObjectLong("ObjectLongOpenCustomHashMap with strategy", - ObjectLongCustomHashMap. newInstance( - HppcMapSyntheticBench.COUNT, HashContainers.DEFAULT_LOAD_FACTOR, - this.INTHOLDER_TRIVIAL_STRATEGY), - HppcMapSyntheticBench.COUNT, HashContainers.DEFAULT_LOAD_FACTOR, getKind, HASH_QUALITY.BAD, Distribution.HIGHBITS); - System.gc(); } public void runMapIterationBench() @@ -756,26 +728,26 @@ private Generator getGenerator(final Distribution disKind, final int pushedEleme switch (disKind) { - case RANDOM: - generator = disGene.RANDOM; - break; + case RANDOM: + generator = disGene.RANDOM; + break; - case CONTIGUOUS: - generator = disGene.LINEAR; - break; + case CONTIGUOUS: + generator = disGene.LINEAR; + break; - case HIGHBITS: - generator = disGene.HIGHBITS; - break; + case HIGHBITS: + generator = disGene.HIGHBITS; + break; - default: - throw new RuntimeException(); + default: + throw new RuntimeException(); } return generator; } -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * main */ diff --git a/hppcrt-template-processor/pom.xml b/hppcrt-template-processor/pom.xml index fb5fd6eda..232e52745 100644 --- a/hppcrt-template-processor/pom.xml +++ b/hppcrt-template-processor/pom.xml @@ -5,14 +5,14 @@ com.github.vsonnier hppcrt-parent - 0.7.2-SNAPSHOT + 0.7.2 ../pom.xml com.github.vsonnier hppcrt-template-processor - 0.7.2-SNAPSHOT + 0.7.2 jar HPPC-RT Template Processor diff --git a/hppcrt/pom.xml b/hppcrt/pom.xml index 4fe52b2c8..36cde52b2 100644 --- a/hppcrt/pom.xml +++ b/hppcrt/pom.xml @@ -6,14 +6,14 @@ com.github.vsonnier hppcrt-parent - 0.7.2-SNAPSHOT + 0.7.2 ../pom.xml com.github.vsonnier hppcrt - 0.7.2-SNAPSHOT + 0.7.2 jar HPPC-RT Collections diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeCustomHashMap.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeCustomHashMap.java deleted file mode 100644 index ab18017c6..000000000 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeCustomHashMap.java +++ /dev/null @@ -1,1829 +0,0 @@ -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.strategies.*; -import com.carrotsearch.hppcrt.hash.*; - -/*! #import("com/carrotsearch/hppcrt/Intrinsics.java") !*/ -/*! ${TemplateOptions.doNotGenerateKType("byte", "char", "short", "float", "double" )} !*/ -/*! #set( $ROBIN_HOOD_FOR_ALL = true) !*/ -/*! #set( $DEBUG = false) !*/ -//If RH is defined, RobinHood Hashing is in effect : -/*! #set( $RH = $ROBIN_HOOD_FOR_ALL) !*/ - -/** - * A hash map of KType to VType, implemented using open - * addressing with linear probing for collision resolution. - *

- * The difference with {@link KTypeVTypeHashMap} is that it uses a - * {@link KTypeHashingStrategy} to compare objects externally instead of using - * the built-in hashCode() / equals(). In particular, the management of null - * keys is up to the {@link KTypeHashingStrategy} 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. - *

- * - *

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 KTypeHashingStrategy} to - * assure good performance.

- * -#if ($TemplateOptions.KTypeGeneric) - *

This implementation supports null keys. In addition, objects passed to the {@link KTypeHashingStrategy} are guaranteed to be not-null.

-#end -#if ($TemplateOptions.VTypeGeneric) - *

This implementation supports null values.

-#end - * - * -#if ($RH) - *

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.

-#end - * - */ -/*! ${TemplateOptions.generatedAnnotation} !*/ -public class KTypeVTypeCustomHashMap -implements KTypeVTypeMap, Cloneable -{ - protected VType defaultValue = Intrinsics. empty(); - - /** - * 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/*! #if ($TemplateOptions.KTypePrimitive) - KType [] - #else !*/ - Object[] - /*! #end !*/ - keys; - - /** - * Hash-indexed array holding all values associated to the keys. - * stored in {@link #keys}. - */ - public/*! #if ($TemplateOptions.VTypePrimitive) - VType [] - #else !*/ - Object[] - /*! #end !*/ - values; - - /*! #if ($RH) !*/ - /** - * #if ($RH) - * Caches the hash value = hash(strategy, keys[i]) & mask, if keys[i] != 0/null, - * for every index i. - * #end - * @see #assigned - */ - /*! #end !*/ - /*! #if ($RH) !*/ - protected int[] hash_cache; - /*! #end !*/ - - /** - * 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 VType 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(); - - /** - * 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 KTypeHashingStrategy hashStrategy; - - /** - * Creates a hash map with the default capacity of {@link Containers#DEFAULT_EXPECTED_ELEMENTS}, - * load factor of {@link HashContainers#DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link KTypeHashingStrategy} - * - *

See class notes about hash distribution importance.

- */ - public KTypeVTypeCustomHashMap(final KTypeHashingStrategy hashStrategy) { - this(Containers.DEFAULT_EXPECTED_ELEMENTS, hashStrategy); - } - - /** - * Creates a hash map with the given initial capacity, default load factor of - * {@link HashContainers#DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link KTypeHashingStrategy} - * - * @param initialCapacity Initial capacity (greater than zero and automatically - * rounded to the next power of two). - */ - public KTypeVTypeCustomHashMap(final int initialCapacity, final KTypeHashingStrategy hashStrategy) { - this(initialCapacity, HashContainers.DEFAULT_LOAD_FACTOR, hashStrategy); - } - - /** - * Creates a hash map with the given initial capacity, - * load factor, using the hashStrategy as {@link KTypeHashingStrategy} - * - * @param loadFactor The load factor (greater than zero and smaller than 1). - * - * - */ - public KTypeVTypeCustomHashMap(final int initialCapacity, final double loadFactor, - final KTypeHashingStrategy hashStrategy) { - - //only accept not-null strategies. - if (hashStrategy == null) { - - throw new IllegalArgumentException("KTypeVTypeOpenCustomHashMap() cannot have a null hashStrategy !"); - } - - this.hashStrategy = hashStrategy; - - this.loadFactor = loadFactor; - //take into account of the load factor to guarantee no reallocations before reaching initialCapacity. - allocateBuffers(HashContainers.minBufferSize(initialCapacity, loadFactor)); - } - - /** - * Creates a hash map from all key-value pairs of another container. - */ - public KTypeVTypeCustomHashMap(final KTypeVTypeAssociativeContainer container, - final KTypeHashingStrategy hashStrategy) { - this(container.size(), hashStrategy); - putAll(container); - } - - /** - * {@inheritDoc} - */ - @Override - public VType put(KType key, VType value) { - if (Intrinsics. isEmpty(key)) { - - if (this.allocatedDefaultKey) { - - final VType 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 KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - int slot = REHASH(strategy, key) & mask; - KType existing; - - /*! #if ($RH) !*/ - final VType[] values = Intrinsics. cast(this.values); - - final int[] cached = this.hash_cache; - - KType tmpKey; - VType tmpValue; - int tmpAllocated; - int initial_slot = slot; - int dist = 0; - int existing_distance = 0; - /*! #end !*/ - - while (!Intrinsics. isEmpty(existing = keys[slot])) { - - if (strategy.equals(key, existing)) { - final VType oldValue = Intrinsics. cast(this.values[slot]); - values[slot] = value; - - return oldValue; - } - - /*! #if ($RH) !*/ - //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; - - /*! #if($DEBUG) !*/ - //Check invariants - - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - assert initial_slot == (REHASH(strategy, key) & mask); - /*! #end !*/ - - dist = existing_distance; - } - /*! #end !*/ - - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //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++; - /*! #if ($RH) !*/ - cached[slot] = initial_slot; - /*! #end !*/ - - keys[slot] = key; - values[slot] = value; - - /*! #if ($RH) !*/ - /*! #if($DEBUG) !*/ - //Check invariants - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - - /*! #end !*/ - /*! #end !*/ - } - return this.defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public int putAll(final KTypeVTypeAssociativeContainer container) { - return putAll((Iterable>) container); - } - - /** - * {@inheritDoc} - */ - @Override - public int putAll(final Iterable> iterable) { - final int count = this.size(); - - for (final KTypeVTypeCursor c : iterable) { - put(c.key, c.value); - } - return this.size() - count; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean putIfAbsent(final KType key, final VType value) { - if (!containsKey(key)) { - put(key, value); - return true; - } - return false; - } - - /*! #if ($TemplateOptions.VTypePrimitive) !*/ - /** - * 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 VType putOrAdd(final KType key, VType putValue, final VType incrementValue) { - - if (containsKey(key)) { - putValue = get(key); - - putValue = (VType) (Intrinsics. add(putValue, incrementValue)); - } - - put(key, putValue); - return putValue; - } - - /*! #end !*/ - - /*! #if ($TemplateOptions.VTypePrimitive) !*/ - /** - * 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 VType addTo(final KType key, final VType incrementValue) - { - return putOrAdd(key, incrementValue, incrementValue); - } - - /*! #end !*/ - - /** - * Expand the internal storage buffers (capacity) and rehash. - */ - private void expandAndPut(final KType pendingKey, final VType pendingValue, final int freeSlot) { - assert this.assigned == this.resizeAt; - - //default sentinel value is never in the keys[] array, so never trigger reallocs - assert !Intrinsics. isEmpty(pendingKey); - - // 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 = Intrinsics. cast(this.keys); - final VType[] oldValues = Intrinsics. cast(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; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - KType key = Intrinsics. empty(); - VType value = Intrinsics. empty(); - - int slot = -1; - - final KType[] keys = Intrinsics. cast(this.keys); - final VType[] values = Intrinsics. cast(this.values); - - /*! #if ($RH) !*/ - final int[] cached = this.hash_cache; - /*! #end !*/ - - /*! #if ($RH) !*/ - KType tmpKey = Intrinsics. empty(); - VType tmpValue = Intrinsics. empty(); - int tmpAllocated = -1; - int initial_slot = -1; - int dist = -1; - int existing_distance = -1; - /*! #end !*/ - - //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;) { - - if (!Intrinsics. isEmpty(key = oldKeys[i])) { - - value = oldValues[i]; - - slot = REHASH2(strategy, key, perturb) & mask; - - /*! #if ($RH) !*/ - initial_slot = slot; - dist = 0; - /*! #end !*/ - - while (is_allocated(slot, keys)) { - /*! #if ($RH) !*/ - //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; - - /*! #if($DEBUG) !*/ - //Check invariants - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - assert initial_slot == (REHASH(strategy, key) & mask); - /*! #end !*/ - - dist = existing_distance; - } - /*! #end !*/ - - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //end while - - /*! #if ($RH) !*/ - cached[slot] = initial_slot; - /*! #end !*/ - - keys[slot] = key; - values[slot] = value; - - /*! #if ($RH) !*/ - /*! #if($DEBUG) !*/ - //Check invariants - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - /*! #end !*/ - /*! #end !*/ - } - } - } - - /** - * 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 = Intrinsics. newArray(capacity); - final VType[] values = Intrinsics. newArray(capacity); - - /*! #if ($RH) !*/ - final int[] cached = new int[capacity]; - /*! #end !*/ - - this.keys = keys; - this.values = values; - - /*! #if ($RH) !*/ - this.hash_cache = cached; - /*! #end !*/ - - //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 VType remove(final KType key) { - if (Intrinsics. isEmpty(key)) { - - if (this.allocatedDefaultKey) { - - final VType previousValue = this.allocatedDefaultKeyValue; - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //help the GC - this.allocatedDefaultKeyValue = Intrinsics. empty(); - /*! #end !*/ - - this.allocatedDefaultKey = false; - return previousValue; - } - - return this.defaultValue; - } - - final int mask = this.keys.length - 1; - - final KType[] keys = Intrinsics. cast(this.keys); - - final KTypeHashingStrategy strategy = this.hashStrategy; - - int slot = REHASH(strategy, key) & mask; - KType existing; - - /*! #if ($RH) !*/ - int dist = 0; - final int[] cached = this.hash_cache; - /*! #end !*/ - - while (!Intrinsics. isEmpty(existing = keys[slot]) - /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - - if (strategy.equals(key, existing)) { - - final VType value = Intrinsics. cast(this.values[slot]); - shiftConflictingKeys(slot); - - return value; - } - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //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 KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - final VType[] values = Intrinsics. cast(this.values); - - /*! #if ($RH) !*/ - final int[] cached = this.hash_cache; - /*! #else - final int perturb = this.perturbation; - #end !*/ - - // 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 VType existingValue = values[slot]; - - if (Intrinsics. isEmpty(existing)) { - break; - } - - /*! #if ($RH) !*/ - //use the cached value, no need to recompute - final int idealSlotModMask = cached[slot]; - /*! #if($DEBUG) !*/ - //Check invariants - assert idealSlotModMask == (REHASH(strategy, existing) & mask); - /*! #end !*/ - /*! #else - final int idealSlotModMask = REHASH2(strategy, existing, perturb) & mask; - #end !*/ - - //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; - - /*! #if ($RH) !*/ - cached[gapSlot] = idealSlotModMask; - /*! #end !*/ - - gapSlot = slot; - distance = 0; - } - } //end while - - // Mark the last found gap slot without a conflict as empty. - keys[gapSlot] = Intrinsics. empty(); - - /* #if ($TemplateOptions.VTypeGeneric) */ - values[gapSlot] = Intrinsics. empty(); - /* #end */ - this.assigned--; - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - @Override - public int removeAll(final KTypeContainer other) { - final int before = this.size(); - - //1) other is a KTypeLookupContainer, 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 KTypeLookupContainer) { - - if (this.allocatedDefaultKey) { - - if (other.contains(Intrinsics. empty())) { - this.allocatedDefaultKey = false; - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //help the GC - this.allocatedDefaultKeyValue = Intrinsics. empty(); - /*! #end !*/ - } - } - - final KType[] keys = Intrinsics. cast(this.keys); - - for (int i = 0; i < keys.length;) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i]) && 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 KTypeCursor c : other) { - - remove(Intrinsics. cast(c.value)); - } - } - - return before - this.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public int removeAll(final KTypePredicate predicate) { - final int before = this.size(); - - if (this.allocatedDefaultKey) { - - if (predicate.apply(Intrinsics. empty())) { - this.allocatedDefaultKey = false; - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //help the GC - this.allocatedDefaultKeyValue = Intrinsics. empty(); - /*! #end !*/ - } - } - - final KType[] keys = Intrinsics. cast(this.keys); - - for (int i = 0; i < keys.length;) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i]) && predicate.apply(existing)) { - - shiftConflictingKeys(i); - // Shift, do not increment slot. - } else { - i++; - } - } - - return before - this.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public int removeAll(final KTypeVTypePredicate predicate) { - - final int before = this.size(); - - if (this.allocatedDefaultKey) { - - if (predicate.apply(Intrinsics. empty(), this.allocatedDefaultKeyValue)) { - this.allocatedDefaultKey = false; - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //help the GC - this.allocatedDefaultKeyValue = Intrinsics. empty(); - /*! #end !*/ - } - } - - final KType[] keys = Intrinsics. cast(this.keys); - final VType[] values = Intrinsics. cast(this.values); - - for (int i = 0; i < keys.length;) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i]) && predicate.apply(existing, values[i])) { - - shiftConflictingKeys(i); - // Shift, do not increment slot. - } else { - i++; - } - } - - return before - this.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public VType get(final KType key) { - if (Intrinsics. isEmpty(key)) { - - if (this.allocatedDefaultKey) { - - return this.allocatedDefaultKeyValue; - } - - return this.defaultValue; - } - - final int mask = this.keys.length - 1; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - - int slot = REHASH(strategy, key) & mask; - KType existing; - - /*! #if ($RH) !*/ - int dist = 0; - final int[] cached = this.hash_cache; - /*! #end !*/ - - while (!Intrinsics. isEmpty(existing = keys[slot]) - /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - - if (strategy.equals(key, existing)) { - - return Intrinsics. cast(this.values[slot]); - } - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //end while true - - return this.defaultValue; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean containsKey(final KType key) { - if (Intrinsics. isEmpty(key)) { - - return this.allocatedDefaultKey; - } - - final int mask = this.keys.length - 1; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - - int slot = REHASH(strategy, key) & mask; - KType existing; - - /*! #if ($RH) !*/ - int dist = 0; - final int[] cached = this.hash_cache; - /*! #end !*/ - - while (!Intrinsics. isEmpty(existing = keys[slot]) - /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - - if (strategy.equals(key, existing)) { - return true; - } - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //end while true - - return false; - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - this.assigned = 0; - - // States are always cleared. - this.allocatedDefaultKey = false; - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //help the GC - this.allocatedDefaultKeyValue = Intrinsics. empty(); - /*! #end !*/ - - //Faster than Arrays.fill(keys, null); // Help the GC. - KTypeArrays.blankArray(this.keys, 0, this.keys.length); - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //Faster than Arrays.fill(values, null); // Help the GC. - VTypeArrays. blankArray(Intrinsics. cast(this.values), 0, this.values.length); - /*! #end !*/ - } - - /** - * {@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() { - final KTypeHashingStrategy strategy = this.hashStrategy; - - int h = 0; - - if (this.allocatedDefaultKey) { - h += BitMixer.mix(this.allocatedDefaultKeyValue); - } - - final KType[] keys = Intrinsics. cast(this.keys); - final VType[] values = Intrinsics. cast(this.values); - - for (int i = keys.length; --i >= 0;) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i])) { - - h += BitMixer.mix(strategy.computeHashCode(existing)) ^ BitMixer.mix(values[i]); - } - } - - return h; - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - @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; - } - - //their hash strategies MUST be "equal", i.e apply the same equivalence criteria. - if (!this.hashStrategy.equals(((KTypeVTypeCustomHashMap) obj).hashStrategy)) { - return false; - } - - final KTypeVTypeCustomHashMap other = (KTypeVTypeCustomHashMap) obj; - - //must be of the same size - if (other.size() != this.size()) { - return false; - } - - final EntryIterator it = this.iterator(); - - while (it.hasNext()) { - final KTypeVTypeCursor c = it.next(); - - if (!other.containsKey(c.key)) { - //recycle - it.release(); - return false; - } - - final VType otherValue = other.get(c.key); - - if (!Intrinsics. equals(c.value, otherValue)) { - //recycle - it.release(); - return false; - } - } //end while - return true; - } - return false; - } - - /** - * An iterator implementation for {@link #iterator}. - * Holds a KTypeVTypeCursor returning - * (key, value, index) = (KType key, VType value, index the position in keys {@link KTypeVTypeCustomHashMap#keys}, or keys.length for key = 0/null) - */ - public final class EntryIterator extends AbstractIterator> - { - public final KTypeVTypeCursor cursor; - - public EntryIterator() { - this.cursor = new KTypeVTypeCursor(); - 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 KTypeVTypeCursor fetch() { - if (this.cursor.index == KTypeVTypeCustomHashMap.this.keys.length + 1) { - - if (KTypeVTypeCustomHashMap.this.allocatedDefaultKey) { - - this.cursor.index = KTypeVTypeCustomHashMap.this.keys.length; - this.cursor.key = Intrinsics. empty(); - this.cursor.value = KTypeVTypeCustomHashMap.this.allocatedDefaultKeyValue; - - return this.cursor; - - } - //no value associated with the default key, continue iteration... - this.cursor.index = KTypeVTypeCustomHashMap.this.keys.length; - } - - int i = this.cursor.index - 1; - - while (i >= 0 && !is_allocated(i, Intrinsics. cast(KTypeVTypeCustomHashMap.this.keys))) { - i--; - } - - if (i == -1) { - return done(); - } - - this.cursor.index = i; - this.cursor.key = Intrinsics. cast(KTypeVTypeCustomHashMap.this.keys[i]); - this.cursor.value = Intrinsics. cast(KTypeVTypeCustomHashMap.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 = KTypeVTypeCustomHashMap.this.keys.length + 1; - } - - @Override - public void reset(final EntryIterator obj) { - /*! #if ($TemplateOptions.KTypeGeneric) !*/ - obj.cursor.key = null; - /*! #end !*/ - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - obj.cursor.value = null; - /*! #end !*/ - } - }); - - /** - * {@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(Intrinsics. empty(), this.allocatedDefaultKeyValue); - } - - final KType[] keys = Intrinsics. cast(this.keys); - final VType[] values = Intrinsics. cast(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 (!Intrinsics. isEmpty(existing = keys[i])) { - procedure.apply(existing, values[i]); - } - } - - return procedure; - } - - /** - * {@inheritDoc} - */ - @Override - public > T forEach(final T predicate) { - if (this.allocatedDefaultKey) { - - if (!predicate.apply(Intrinsics. empty(), this.allocatedDefaultKeyValue)) { - - return predicate; - } - } - - final KType[] keys = Intrinsics. cast(this.keys); - final VType[] values = Intrinsics. cast(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 (!Intrinsics. isEmpty(existing = keys[i])) { - 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 AbstractKTypeCollection implements KTypeLookupContainer - { - private final KTypeVTypeCustomHashMap owner = KTypeVTypeCustomHashMap.this; - - @Override - public boolean contains(final KType e) { - return containsKey(e); - } - - @Override - public > T forEach(final T procedure) { - - if (this.owner.allocatedDefaultKey) { - - procedure.apply(Intrinsics. empty()); - } - - final KType[] keys = Intrinsics. cast(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 (!Intrinsics. isEmpty(existing = keys[i])) { - procedure.apply(existing); - } - } - - return procedure; - } - - @Override - public > T forEach(final T predicate) { - if (this.owner.allocatedDefaultKey) { - - if (!predicate.apply(Intrinsics. empty())) { - - return predicate; - } - } - - final KType[] keys = Intrinsics. cast(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 (!Intrinsics. isEmpty(existing = keys[i])) { - 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 KTypePredicate 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 = KTypeVTypeCustomHashMap.this.keys.length + 1; - } - - @Override - public void reset(final KeysIterator obj) { - /*! #if ($TemplateOptions.KTypeGeneric) !*/ - obj.cursor.value = null; - /*! #end !*/ - - } - }); - - @Override - public KType[] toArray(final KType[] target) { - int count = 0; - - if (this.owner.allocatedDefaultKey) { - - target[count++] = Intrinsics. empty(); - } - - final KType[] keys = Intrinsics. cast(this.owner.keys); - - for (int i = 0; i < keys.length; i++) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i])) { - target[count++] = existing; - } - } - - assert count == this.owner.size(); - return target; - } - }; - - /** - * An iterator over the set of keys. - * Holds a KTypeCursor returning (value, index) = (KType key, index the position in buffer {@link KTypeVTypeCustomHashMap#keys}, or keys.length for key = 0/null.) - */ - public final class KeysIterator extends AbstractIterator> - { - public final KTypeCursor cursor; - - public KeysIterator() { - this.cursor = new KTypeCursor(); - 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 KTypeCursor fetch() { - - if (this.cursor.index == KTypeVTypeCustomHashMap.this.keys.length + 1) { - - if (KTypeVTypeCustomHashMap.this.allocatedDefaultKey) { - - this.cursor.index = KTypeVTypeCustomHashMap.this.keys.length; - this.cursor.value = Intrinsics. empty(); - - return this.cursor; - - } - //no value associated with the default key, continue iteration... - this.cursor.index = KTypeVTypeCustomHashMap.this.keys.length; - } - - int i = this.cursor.index - 1; - - while (i >= 0 && !is_allocated(i, Intrinsics. cast(KTypeVTypeCustomHashMap.this.keys))) { - i--; - } - - if (i == -1) { - return done(); - } - - this.cursor.index = i; - this.cursor.value = Intrinsics. cast(KTypeVTypeCustomHashMap.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 AbstractKTypeCollection - { - private final KTypeVTypeCustomHashMap owner = KTypeVTypeCustomHashMap.this; - - /** - * {@inheritDoc} - */ - @Override - public int size() { - return this.owner.size(); - } - - /** - * {@inheritDoc} - */ - @Override - public int capacity() { - - return this.owner.capacity(); - } - - @Override - public boolean contains(final VType value) { - if (this.owner.allocatedDefaultKey && Intrinsics. equals(value, 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 = Intrinsics. cast(this.owner.keys); - final VType[] values = Intrinsics. cast(this.owner.values); - - for (int slot = 0; slot < keys.length; slot++) { - if (is_allocated(slot, keys) && Intrinsics. equals(value, 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 = Intrinsics. cast(this.owner.keys); - final VType[] values = Intrinsics. cast(this.owner.values); - - for (int slot = 0; slot < keys.length; slot++) { - if (is_allocated(slot, keys)) { - 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 = Intrinsics. cast(this.owner.keys); - final VType[] values = Intrinsics. cast(this.owner.values); - - for (int slot = 0; slot < keys.length; slot++) { - if (is_allocated(slot, keys)) { - 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 VType e) { - final int before = this.owner.size(); - - if (this.owner.allocatedDefaultKey) { - - if (Intrinsics. equals(e, this.owner.allocatedDefaultKeyValue)) { - - this.owner.allocatedDefaultKey = false; - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //help the GC - this.owner.allocatedDefaultKeyValue = Intrinsics. empty(); - /*! #end !*/ - } - } - - final KType[] keys = Intrinsics. cast(this.owner.keys); - final VType[] values = Intrinsics. cast(this.owner.values); - - for (int slot = 0; slot < keys.length;) { - if (is_allocated(slot, keys) && Intrinsics. equals(e, 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 KTypePredicate predicate) { - final int before = this.owner.size(); - - if (this.owner.allocatedDefaultKey) { - - if (predicate.apply(this.owner.allocatedDefaultKeyValue)) { - - this.owner.allocatedDefaultKey = false; - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - //help the GC - this.owner.allocatedDefaultKeyValue = Intrinsics. empty(); - /*! #end !*/ - } - } - - final KType[] keys = Intrinsics. cast(this.owner.keys); - final VType[] values = Intrinsics. cast(this.owner.values); - - for (int slot = 0; slot < keys.length;) { - if (is_allocated(slot, keys) && 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, ValuesIterator> valuesIteratorPool = new IteratorPool, ValuesIterator>( - new ObjectFactory() { - - @Override - public ValuesIterator create() { - - return new ValuesIterator(); - } - - @Override - public void initialize(final ValuesIterator obj) { - obj.cursor.index = KTypeVTypeCustomHashMap.this.keys.length + 1; - } - - @Override - public void reset(final ValuesIterator obj) { - - /*! #if ($TemplateOptions.VTypeGeneric) !*/ - obj.cursor.value = null; - /*! #end !*/ - - } - }); - - @Override - public VType[] toArray(final VType[] target) { - int count = 0; - - if (this.owner.allocatedDefaultKey) { - - target[count++] = this.owner.allocatedDefaultKeyValue; - } - - final KType[] keys = Intrinsics. cast(this.owner.keys); - final VType[] values = Intrinsics. cast(this.owner.values); - - for (int i = 0; i < values.length; i++) { - if (is_allocated(i, keys)) { - target[count++] = values[i]; - } - } - - assert count == this.owner.size(); - return target; - } - } - - /** - * An iterator over the set of values. - * Holds a KTypeCursor returning (value, index) = (VType value, index the position in buffer {@link KTypeVTypeCustomHashMap#values}, - * or values.length for value = {@link KTypeVTypeCustomHashMap#allocatedDefaultKeyValue}). - */ - public final class ValuesIterator extends AbstractIterator> - { - public final KTypeCursor cursor; - - public ValuesIterator() { - this.cursor = new KTypeCursor(); - 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 KTypeCursor fetch() { - - if (this.cursor.index == KTypeVTypeCustomHashMap.this.values.length + 1) { - - if (KTypeVTypeCustomHashMap.this.allocatedDefaultKey) { - - this.cursor.index = KTypeVTypeCustomHashMap.this.values.length; - this.cursor.value = KTypeVTypeCustomHashMap.this.allocatedDefaultKeyValue; - - return this.cursor; - - } - //no value associated with the default key, continue iteration... - this.cursor.index = KTypeVTypeCustomHashMap.this.keys.length; - } - - int i = this.cursor.index - 1; - - while (i >= 0 && !is_allocated(i, Intrinsics. cast(KTypeVTypeCustomHashMap.this.keys))) { - i--; - } - - if (i == -1) { - return done(); - } - - this.cursor.index = i; - this.cursor.value = Intrinsics. cast(KTypeVTypeCustomHashMap.this.values[i]); - - return this.cursor; - } - } - - /** - * {@inheritDoc} - * #if ($TemplateOptions.AnyGeneric) - * The returned clone will use the same HashingStrategy strategy. - * #end - */ - @Override - public KTypeVTypeCustomHashMap clone() { - //clone to size to prevent exponential growth - final KTypeVTypeCustomHashMap cloned = new KTypeVTypeCustomHashMap(this.size(), - this.loadFactor, this.hashStrategy); - - //We must NOT clone because of independent perturbations seeds - cloned.putAll(this); - - cloned.allocatedDefaultKeyValue = this.allocatedDefaultKeyValue; - cloned.allocatedDefaultKey = this.allocatedDefaultKey; - 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 KTypeVTypeCursor 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 KTypeVTypeCustomHashMap from(final KType[] keys, final VType[] values, - final KTypeHashingStrategy hashStrategy) { - if (keys.length != values.length) { - throw new IllegalArgumentException("Arrays of keys and values must have an identical length."); - } - - final KTypeVTypeCustomHashMap map = new KTypeVTypeCustomHashMap(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. Default load factor is used. - */ - public static KTypeVTypeCustomHashMap from( - final KTypeVTypeAssociativeContainer container, - final KTypeHashingStrategy hashStrategy) { - return new KTypeVTypeCustomHashMap(container, hashStrategy); - } - - /** - * Create a new hash map without providing the full generic signature (constructor - * shortcut). - */ - public static KTypeVTypeCustomHashMap newInstance( - final KTypeHashingStrategy hashStrategy) { - return new KTypeVTypeCustomHashMap(hashStrategy); - } - - /** - * Create a new hash map with initial capacity and load factor control. (constructor - * shortcut). - */ - public static KTypeVTypeCustomHashMap newInstance(final int initialCapacity, - final double loadFactor, final KTypeHashingStrategy hashStrategy) { - return new KTypeVTypeCustomHashMap(initialCapacity, loadFactor, hashStrategy); - } - - /** - * Return the current {@link KTypeHashingStrategy} in use. - */ - public KTypeHashingStrategy strategy() { - return this.hashStrategy; - } - - /** - * Returns the "default value" value used - * in containers methods returning "default value" - */ - @Override - public VType getDefaultValue() { - return this.defaultValue; - } - - /** - * Set the "default value" value to be used - * in containers methods returning "default value" - */ - @Override - public void setDefaultValue(final VType defaultValue) { - this.defaultValue = defaultValue; - } - - //Test for existence in template - /*! #if ($TemplateOptions.declareInline("is_allocated(slot, keys)", - "<*,*>==>!Intrinsics.isEmpty(keys[slot])")) !*/ - /** - * template version - * (actual method is inlined in generated code) - */ - private boolean is_allocated(final int slot, final KType[] keys) { - - return !Intrinsics. isEmpty(keys[slot]); - } - - /*! #end !*/ - - /*! #if ($TemplateOptions.declareInline("probe_distance(slot, cache)", - "<*,*>==>slot < cache[slot] ? slot + cache.length - cache[slot] : slot - cache[slot]")) !*/ - /** - * (actual method is inlined in generated code) - */ - private int probe_distance(final int slot, final int[] cache) { - - final int rh = cache[slot]; - - /*! #if($DEBUG) !*/ - //Check : cached hashed slot is == computed value - final int mask = cache.length - 1; - assert rh == (REHASH(this.hashStrategy, Intrinsics. cast(this.keys[slot])) & mask); - /*! #end !*/ - - if (slot < rh) { - //wrap around - return slot + cache.length - rh; - } - - return slot - rh; - } - - /*! #end !*/ - - /*! #if ($TemplateOptions.declareInline("REHASH(strategy, value)", - "<*,*>==>BitMixer.mix(strategy.computeHashCode(value) , this.perturbation )")) !*/ - /** - * (actual method is inlined in generated code) - */ - private int REHASH(final KTypeHashingStrategy strategy, final KType value) { - - return BitMixer.mix(strategy.computeHashCode(value), this.perturbation); - } - - /*! #end !*/ - - /*! #if ($TemplateOptions.declareInline("REHASH2(strategy, value, perturb)", - "<*,*>==>BitMixer.mix(strategy.computeHashCode(value) , perturb)")) !*/ - /** - * REHASH2 method for rehashing the keys with perturbation seed as parameter - * (inlined in generated code) - * Thanks to single array mode, no need to check for null/0 or booleans. - */ - private int REHASH2(final KTypeHashingStrategy strategy, final KType value, final int perturb) { - - return BitMixer.mix(strategy.computeHashCode(value), perturb); - } - /*! #end !*/ -} diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMap.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMap.java index d5222850a..8403a5af9 100644 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMap.java +++ b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMap.java @@ -16,6 +16,13 @@ * A hash map of KType to VType, implemented using open * addressing with linear probing for collision resolution. * +#if ($TemplateOptions.KTypeGeneric) + *

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. + *

+#end *

* The internal buffers of this implementation ({@link #keys}, {@link #values}), * are always allocated to the nearest size that is a power of two. When @@ -121,6 +128,45 @@ public class KTypeVTypeHashMap */ private final int perturbation = Containers.randomSeed32(); + /*! #if ($TemplateOptions.KTypeGeneric) !*/ + + /** + * 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 KType 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 KType to be compared + * @param b not-null KType 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 Intrinsics. equalsNotNull(a, b); + } + + /*! #end !*/ + /** * Default constructor: Creates a hash map with the default capacity of {@link Containers#DEFAULT_EXPECTED_ELEMENTS}, * load factor of {@link HashContainers#DEFAULT_LOAD_FACTOR}. @@ -208,7 +254,7 @@ public VType put(KType key, VType value) { while (!Intrinsics. isEmpty(existing = keys[slot])) { - if (Intrinsics. equalsNotNull(key, existing)) { + if (KEYEQUALS(key, existing)) { final VType oldValue = Intrinsics. cast(this.values[slot]); values[slot] = value; @@ -546,7 +592,7 @@ public VType remove(final KType key) { while (!Intrinsics. isEmpty(existing = keys[slot]) /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - if (Intrinsics. equalsNotNull(key, existing)) { + if (KEYEQUALS(key, existing)) { final VType value = Intrinsics. cast(this.values[slot]); @@ -786,7 +832,7 @@ public VType get(final KType key) { while (!Intrinsics. isEmpty(existing = keys[slot]) /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - if (Intrinsics. equalsNotNull(key, existing)) { + if (KEYEQUALS(key, existing)) { return Intrinsics. cast(this.values[slot]); } @@ -826,7 +872,7 @@ public boolean containsKey(final KType key) { while (!Intrinsics. isEmpty(existing = keys[slot]) /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - if (Intrinsics. equalsNotNull(key, existing)) { + if (KEYEQUALS(key, existing)) { return true; } slot = (slot + 1) & mask; @@ -1753,7 +1799,7 @@ private int probe_distance(final int slot, final int[] cache) { /*! #end !*/ /*! #if ($TemplateOptions.declareInline("REHASH(value)", - "==>BitMixer.mix(value.hashCode() , this.perturbation)", + "==>BitMixer.mix(hashKey(value) , this.perturbation)", "<*,*>==>BitMixer.mix(value , this.perturbation)")) !*/ /** * REHASH method for rehashing the keys. @@ -1762,13 +1808,13 @@ private int probe_distance(final int slot, final int[] cache) { */ private int REHASH(final KType value) { - return BitMixer.mix(value.hashCode(), this.perturbation); + return BitMixer.mix(hashKey(value), this.perturbation); } /*! #end !*/ /*! #if ($TemplateOptions.declareInline("REHASH2(value, perturb)", - "==>BitMixer.mix(value.hashCode() , perturb)", + "==>BitMixer.mix(hashKey(value) , perturb)", "<*,*>==>BitMixer.mix(value , perturb)")) !*/ /** * REHASH2 method for rehashing the keys with perturbation seed as parameter @@ -1777,7 +1823,19 @@ private int REHASH(final KType value) { */ private int REHASH2(final KType value, final int perturb) { - return BitMixer.mix(value.hashCode(), perturb); + return BitMixer.mix(hashKey(value), perturb); + } + /*! #end !*/ + + /*! #if ($TemplateOptions.declareInline("KEYEQUALS(key1, key2)", + "==>equalKeys(key1, key2)", + "<*,*>==>Intrinsics. equalsNotNull(key1, key2)")) !*/ + /** + * macro which hides the applied equality criteria + */ + private boolean KEYEQUALS(final KType key1, final KType key2) { + + return equalKeys(key1, key2); } /*! #end !*/ } diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeIdentityHashMap.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeIdentityHashMap.java index 0db9b559a..57b32a320 100644 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeIdentityHashMap.java +++ b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeIdentityHashMap.java @@ -10,7 +10,7 @@ * * The difference with {@link KTypeVTypeHashMap} is that it uses direct Object reference equality for comparison and * direct "address" {@link System#identityHashCode(Object)} for hashCode(), instead of using - * the built-in hashCode() / equals(). + * the built-in {@link #hashCode()} / {@link #equals(Object)}. * *

This implementation supports null keys.

* @@ -22,9 +22,26 @@ */ /*! ${TemplateOptions.generatedAnnotation} !*/ public final class KTypeVTypeIdentityHashMap - extends KTypeVTypeCustomHashMap +extends KTypeVTypeHashMap { - private static final KTypeIdentityHash IDENTITY_EQUALITY = new KTypeIdentityHash(); + /** + * Hash customization to only consider the identity hash code. + */ + @Override + protected final int hashKey(final KType key) { + + return System.identityHashCode(key); + } + + /** + * Equality customization to only consider object identity, comparing + * instances directly by ==. + */ + @Override + protected final boolean equalKeys(final KType a, final KType b) { + + return (a == b); + } /** * Default constructor: Creates a hash map with the default capacity of {@link Containers#DEFAULT_EXPECTED_ELEMENTS}, @@ -60,10 +77,9 @@ public KTypeVTypeIdentityHashMap(final int initialCapacity) * * */ - @SuppressWarnings({ "cast", "unchecked" }) public KTypeVTypeIdentityHashMap(final int initialCapacity, final double loadFactor) { - super(initialCapacity, loadFactor, (KTypeIdentityHash) KTypeVTypeIdentityHashMap.IDENTITY_EQUALITY); + super(initialCapacity, loadFactor); } /** @@ -136,41 +152,4 @@ public static KTypeVTypeIdentityHashMap newInstance { return new KTypeVTypeIdentityHashMap(initialCapacity, loadFactor); } - - /** - * Inherited from KTypeVTypeOpenCustomHashMap, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static final KTypeVTypeIdentityHashMap newInstance(final KTypeHashingStrategy hashStrategy) - { - throw new RuntimeException("Identity hash newInstance(strategy) usage logical error"); - } - - /** - * Inherited from KTypeVTypeOpenCustomHashMap, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static final KTypeVTypeIdentityHashMap newInstance(final int initialCapacity, final double loadFactor, final KTypeHashingStrategy hashStrategy) - { - throw new RuntimeException("Identity hash newInstance(capacity, loadfactor, strategy) usage logical error"); - } - - /** - * Inherited from KTypeVTypeOpenCustomHashMap, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static final KTypeVTypeIdentityHashMap from(final KType[] keys, final VType[] values, final KTypeHashingStrategy hashStrategy) - { - throw new RuntimeException("Identity hash from(keys,values,strategy) usage logical error"); - } - - /** - * Inherited from KTypeVTypeOpenCustomHashMap, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static final KTypeVTypeIdentityHashMap from(final KTypeVTypeAssociativeContainer container, - final KTypeHashingStrategy hashStrategy) - { - throw new RuntimeException("Identity hash from(KTypeVTypeAssociativeContainer, strategy) usage logical error"); - } } diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeCustomHashSet.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeCustomHashSet.java deleted file mode 100644 index 32862a599..000000000 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeCustomHashSet.java +++ /dev/null @@ -1,1027 +0,0 @@ -package com.carrotsearch.hppcrt.sets; - -import com.carrotsearch.hppcrt.*; -import com.carrotsearch.hppcrt.cursors.*; -import com.carrotsearch.hppcrt.predicates.*; -import com.carrotsearch.hppcrt.procedures.*; -import com.carrotsearch.hppcrt.strategies.*; -import com.carrotsearch.hppcrt.hash.*; - -/*! #import("com/carrotsearch/hppcrt/Intrinsics.java") !*/ -/*! ${TemplateOptions.doNotGenerateKType( "byte", "char", "short", "float", "double" )} !*/ -/*! #set( $ROBIN_HOOD_FOR_ALL = true) !*/ -/*! #set( $DEBUG = false) !*/ -//If RH is defined, RobinHood Hashing is in effect : -/*! #set( $RH = $ROBIN_HOOD_FOR_ALL) !*/ - -/** - * A hash set of KTypes, implemented using using open - * addressing with linear probing for collision resolution. - * - *

- * The difference with {@link KTypeHashSet} is that it uses a - * {@link KTypeHashingStrategy} to compare objects externally instead of using - * the built-in hashCode() / equals(). In particular, the management of null - * keys is up to the {@link KTypeHashingStrategy} implementation. - * - *

- * The internal buffers of this implementation ({@link #keys}, etc...) - * 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 KTypeHashingStrategy} to - * assure good performance.

- * - * -#if ($TemplateOptions.KTypeGeneric) - *

This implementation supports null keys. In addition, objects passed to the {@link KTypeHashingStrategy} are guaranteed to be not-null.

-#end - * - * -#if ($RH) - *

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.

-#end - * - */ -/*! ${TemplateOptions.generatedAnnotation} !*/ -public class KTypeCustomHashSet -extends AbstractKTypeCollection -implements KTypeLookupContainer, KTypeSet, Cloneable -{ - /** - * Hash-indexed array holding all set entries. - *

- * Direct set iteration: iterate {keys[i]} for i in [0; keys.length[ where keys[i] != 0/null, then also - * {0/null} is in the set if {@link #allocatedDefaultKey} = true. - *

- */ - public/*! #if ($TemplateOptions.KTypePrimitive) - KType [] - #else !*/ - Object[] - /*! #end !*/ - keys; - - /*! #if ($RH) !*/ - /** - * #if ($RH) - * Caches the hash value = hash(keys[i]) & mask, if keys[i] != 0/null, - * for every index i. - * #end - * @see #assigned - */ - /*! #end !*/ - /*! #if ($RH) !*/ - protected int[] hash_cache; - /*! #end !*/ - - /** - * True if key = 0/null is in the map. - */ - public boolean allocatedDefaultKey = false; - - /** - * 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(); - - /** - * 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 KTypeHashingStrategy hashStrategy; - - /** - * Creates a hash set with the default capacity of {@link Containers#DEFAULT_EXPECTED_ELEMENTS}, - * load factor of {@link HashContainers#DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link KTypeHashingStrategy} - */ - public KTypeCustomHashSet(final KTypeHashingStrategy hashStrategy) { - this(Containers.DEFAULT_EXPECTED_ELEMENTS, HashContainers.DEFAULT_LOAD_FACTOR, hashStrategy); - } - - /** - * Creates a hash set with the given capacity, - * load factor of {@link HashContainers#DEFAULT_LOAD_FACTOR}, using the hashStrategy as {@link KTypeHashingStrategy} - */ - public KTypeCustomHashSet(final int initialCapacity, final KTypeHashingStrategy hashStrategy) { - this(initialCapacity, HashContainers.DEFAULT_LOAD_FACTOR, hashStrategy); - } - - /** - * Creates a hash set with the given capacity and load factor, using the hashStrategy as {@link KTypeHashingStrategy} - */ - public KTypeCustomHashSet(final int initialCapacity, final double loadFactor, - final KTypeHashingStrategy hashStrategy) { - - //only accept not-null strategies. - if (hashStrategy == null) { - - throw new IllegalArgumentException("KTypeOpenCustomHashSet() cannot have a null hashStrategy !"); - } - - this.hashStrategy = hashStrategy; - - this.loadFactor = loadFactor; - //take into account of the load factor to guarantee no reallocations before reaching initialCapacity. - allocateBuffers(HashContainers.minBufferSize(initialCapacity, loadFactor)); - } - - /** - * Creates a hash set from elements of another container. Default load factor is used. - */ - public KTypeCustomHashSet(final KTypeContainer container, - final KTypeHashingStrategy hashStrategy) { - this(container.size(), hashStrategy); - addAll(container); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean add(KType e) { - if (Intrinsics. isEmpty(e)) { - - if (this.allocatedDefaultKey) { - - return false; - } - - this.allocatedDefaultKey = true; - - return true; - } - - final int mask = this.keys.length - 1; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - - int slot = REHASH(strategy, e) & mask; - KType existing; - - /*! #if ($RH) !*/ - final int[] cached = this.hash_cache; - KType tmpKey; - int tmpAllocated; - int initial_slot = slot; - int dist = 0; - int existing_distance = 0; - /*! #end !*/ - - while (!Intrinsics. isEmpty(existing = keys[slot])) { - if (strategy.equals(e, existing)) { - return false; - } - - /*! #if ($RH) !*/ - //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] = e; - e = tmpKey; - - tmpAllocated = cached[slot]; - cached[slot] = initial_slot; - initial_slot = tmpAllocated; - - /*! #if($DEBUG) !*/ - //Check invariants - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - assert initial_slot == (REHASH(strategy, e) & mask); - /*! #end !*/ - - dist = existing_distance; - } - /*! #end !*/ - - slot = (slot + 1) & mask; - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } - - // Check if we need to grow. If so, reallocate new data, - // fill in the last element and rehash. - if (this.assigned == this.resizeAt) { - - expandAndAdd(e, slot); - } else { - this.assigned++; - /*! #if ($RH) !*/ - cached[slot] = initial_slot; - /*! #end !*/ - - keys[slot] = e; - - /*! #if ($RH) !*/ - /*! #if($DEBUG) !*/ - //Check invariants - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - /*! #end !*/ - /*! #end !*/ - } - return true; - } - - /** - * Adds two elements to the set. - */ - public int add(final KType e1, final KType e2) { - int count = 0; - if (add(e1)) { - count++; - } - if (add(e2)) { - count++; - } - return count; - } - - /** - * Vararg-signature method for adding elements to this set. - *

This method is handy, but costly if used in tight loops (anonymous - * array passing)

- * - * @return Returns the number of elements that were added to the set - * (were not present in the set). - */ - public int add(final KType... elements) { - int count = 0; - for (final KType e : elements) { - if (add(e)) { - count++; - } - } - return count; - } - - /** - * {@inheritDoc} - */ - @Override - public int addAll(final KTypeContainer container) { - return addAll((Iterable>) container); - } - - /** - * {@inheritDoc} - */ - @Override - public int addAll(final Iterable> iterable) { - int count = 0; - for (final KTypeCursor cursor : iterable) { - if (add(cursor.value)) { - count++; - } - } - return count; - } - - /** - * Expand the internal storage buffers (capacity) or rehash current - * keys and values if there are a lot of deleted slots. - */ - private void expandAndAdd(final KType pendingKey, final int freeSlot) { - assert this.assigned == this.resizeAt; - - //default sentinel value is never in the keys[] array, so never trigger reallocs - assert (!Intrinsics. isEmpty(pendingKey)); - - // 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 = Intrinsics. cast(this.keys); - - 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; - - //Variables for adding - final int mask = this.keys.length - 1; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - KType e = Intrinsics. empty(); - //adding phase - int slot = -1; - - final KType[] keys = Intrinsics. cast(this.keys); - - /*! #if ($RH) !*/ - final int[] cached = this.hash_cache; - /*! #end !*/ - - /*! #if ($RH) !*/ - KType tmpKey = Intrinsics. empty(); - int tmpAllocated = -1; - int initial_slot = -1; - int dist = -1; - int existing_distance = -1; - /*! #end !*/ - - //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;) { - - if (!Intrinsics. isEmpty(e = oldKeys[i])) { - - slot = REHASH2(strategy, e, perturb) & mask; - - /*! #if ($RH) !*/ - initial_slot = slot; - dist = 0; - /*! #end !*/ - - while (is_allocated(slot, keys)) { - /*! #if ($RH) !*/ - //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] = e; - e = tmpKey; - - tmpAllocated = cached[slot]; - cached[slot] = initial_slot; - initial_slot = tmpAllocated; - - /*! #if($DEBUG) !*/ - //Check invariants - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - assert initial_slot == (REHASH(strategy, e) & mask); - /*! #end !*/ - - dist = existing_distance; - } //endif - /*! #end !*/ - - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //end while - - //place it at that position - /*! #if ($RH) !*/ - cached[slot] = initial_slot; - /*! #end !*/ - - keys[slot] = e; - - /*! #if ($RH) !*/ - /*! #if($DEBUG) !*/ - //Check invariants - assert cached[slot] == (REHASH(strategy, keys[slot]) & mask); - /*! #end !*/ - /*! #end !*/ - } - } - } - - /** - * 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 = Intrinsics. newArray(capacity); - - /*! #if ($RH) !*/ - final int[] allocated = new int[capacity]; - /*! #end !*/ - - this.keys = keys; - - /*! #if ($RH) !*/ - this.hash_cache = allocated; - /*! #end !*/ - - //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 int removeAll(final KType key) { - return remove(key) ? 1 : 0; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean remove(final KType key) { - if (Intrinsics. isEmpty(key)) { - - if (this.allocatedDefaultKey) { - - this.allocatedDefaultKey = false; - return true; - } - - return false; - } - - final int mask = this.keys.length - 1; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - - int slot = REHASH(strategy, key) & mask; - KType existing; - - /*! #if ($RH) !*/ - int dist = 0; - final int[] cached = this.hash_cache; - /*! #end !*/ - - while (!Intrinsics. isEmpty(existing = keys[slot]) - /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - if (strategy.equals(key, existing)) { - - shiftConflictingKeys(slot); - return true; - } - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //end while true - - return false; - } - - /** - * Shift all the slot-conflicting keys allocated to (and including) slot. - */ - private void shiftConflictingKeys(int gapSlot) { - final int mask = this.keys.length - 1; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - - /*! #if ($RH) !*/ - final int[] cached = this.hash_cache; - /*! #else - final int perturb = this.perturbation; - #end !*/ - - // 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]; - - if (Intrinsics. isEmpty(existing)) { - break; - } - - /*! #if ($RH) !*/ - //use the cached value, no need to recompute - final int idealSlotModMask = cached[slot]; - /*! #if($DEBUG) !*/ - //Check invariants - assert idealSlotModMask == (REHASH(strategy, existing) & mask); - /*! #end !*/ - /*! #else - final int idealSlotModMask = REHASH2(strategy, existing, perturb) & mask; - #end !*/ - - //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; - - /*! #if ($RH) !*/ - cached[gapSlot] = idealSlotModMask; - /*! #end !*/ - - gapSlot = slot; - distance = 0; - } - } //end while - - // Mark the last found gap slot without a conflict as empty. - keys[gapSlot] = Intrinsics. empty(); - - this.assigned--; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean contains(final KType key) { - if (Intrinsics. isEmpty(key)) { - - return this.allocatedDefaultKey; - } - - final int mask = this.keys.length - 1; - - final KTypeHashingStrategy strategy = this.hashStrategy; - - final KType[] keys = Intrinsics. cast(this.keys); - - int slot = REHASH(strategy, key) & mask; - KType existing; - - /*! #if ($RH) !*/ - final int[] cached = this.hash_cache; - int dist = 0; - /*! #end !*/ - - while (!Intrinsics. isEmpty(existing = keys[slot]) - /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - if (strategy.equals(key, existing)) { - return true; - } - slot = (slot + 1) & mask; - - /*! #if ($RH) !*/ - dist++; - /*! #end !*/ - } //end while true - - return false; - } - - /** - * {@inheritDoc} - * - *

Does not release internal buffers.

- */ - @Override - public void clear() { - this.assigned = 0; - - // States are always cleared. - this.allocatedDefaultKey = false; - - //Faster than Arrays.fill(keys, null); // Help the GC. - KTypeArrays.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} - */ - @Override - public int hashCode() { - final KTypeHashingStrategy strategy = this.hashStrategy; - - int h = 0; - //allocated default key has hash = 0 - - final KType[] keys = Intrinsics. cast(this.keys); - - for (int i = keys.length; --i >= 0;) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i])) { - - h += BitMixer.mix(strategy.computeHashCode(existing)); - } - } - - return h; - } - - /** - * {@inheritDoc} - */ - @SuppressWarnings("unchecked") - @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; - } - - //their hash strategies MUST be "equal", i.e apply the same equivalence criteria. - if (!this.hashStrategy.equals(((KTypeCustomHashSet) obj).hashStrategy)) { - - return false; - } - - final KTypeCustomHashSet other = (KTypeCustomHashSet) obj; - - //must be of the same size - if (other.size() != this.size()) { - return false; - } - - final EntryIterator it = this.iterator(); - - while (it.hasNext()) { - if (!other.contains(it.next().value)) { - //recycle - it.release(); - return false; - } - } - return true; - } - return false; - } - - /** - * An iterator implementation for {@link #iterator}. - * Holds a KTypeCursor returning (value, index) = (KType value, index the position in {@link KTypeCustomHashSet#keys}, or keys.length for key = 0/null.) - */ - public final class EntryIterator extends AbstractIterator> - { - public final KTypeCursor cursor; - - public EntryIterator() { - this.cursor = new KTypeCursor(); - 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 KTypeCursor fetch() { - if (this.cursor.index == KTypeCustomHashSet.this.keys.length + 1) { - - if (KTypeCustomHashSet.this.allocatedDefaultKey) { - - this.cursor.index = KTypeCustomHashSet.this.keys.length; - this.cursor.value = Intrinsics. empty(); - - return this.cursor; - - } - //no value associated with the default key, continue iteration... - this.cursor.index = KTypeCustomHashSet.this.keys.length; - } - - int i = this.cursor.index - 1; - - while (i >= 0 && !is_allocated(i, Intrinsics. cast(KTypeCustomHashSet.this.keys))) { - i--; - } - - if (i == -1) { - return done(); - } - - this.cursor.index = i; - this.cursor.value = Intrinsics. cast(KTypeCustomHashSet.this.keys[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 = KTypeCustomHashSet.this.keys.length + 1; - } - - @Override - public void reset(final EntryIterator obj) { - /*! #if ($TemplateOptions.KTypeGeneric) !*/ - obj.cursor.value = null; - /*! #end !*/ - - } - }); - - /** - * {@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(Intrinsics. empty()); - } - - final KType[] keys = Intrinsics. cast(this.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 (!Intrinsics. isEmpty(existing = keys[i])) { - procedure.apply(existing); - } - } - - return procedure; - } - - /** - * {@inheritDoc} - */ - @Override - public KType[] toArray(final KType[] target) { - int count = 0; - - if (this.allocatedDefaultKey) { - - target[count++] = Intrinsics. empty(); - } - - final KType[] keys = Intrinsics. cast(this.keys); - - for (int i = 0; i < keys.length; i++) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i])) { - target[count++] = existing; - } - } - assert count == this.size(); - - return target; - } - - /** - * {@inheritDoc} - */ - @Override - public KTypeCustomHashSet clone() { - //clone to size() to prevent eventual exponential growth - final KTypeCustomHashSet cloned = new KTypeCustomHashSet(this.size(), this.loadFactor, - this.hashStrategy); - - //We must NOT clone because of the independent perturbation seeds - cloned.addAll(this); - - cloned.allocatedDefaultKey = this.allocatedDefaultKey; - - return cloned; - } - - /** - * {@inheritDoc} - */ - @Override - public > T forEach(final T predicate) { - - if (this.allocatedDefaultKey) { - - if (!predicate.apply(Intrinsics. empty())) { - - return predicate; - } - } - - final KType[] keys = Intrinsics. cast(this.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 (!Intrinsics. isEmpty(existing = keys[i])) { - if (!predicate.apply(existing)) { - break; - } - } - } - - return predicate; - } - - /** - * {@inheritDoc} - */ - @Override - public int removeAll(final KTypePredicate predicate) { - final int before = this.size(); - - if (this.allocatedDefaultKey) { - - if (predicate.apply(Intrinsics. empty())) { - this.allocatedDefaultKey = false; - } - } - - final KType[] keys = Intrinsics. cast(this.keys); - - for (int i = 0; i < keys.length;) { - KType existing; - if (!Intrinsics. isEmpty(existing = keys[i]) && predicate.apply(existing)) { - - shiftConflictingKeys(i); - // Shift, do not increment slot. - } else { - i++; - } - } - - return before - this.size(); - } - - /** - * Create a set from a variable number of arguments or an array of KType. - */ - public static KTypeCustomHashSet from(final KTypeHashingStrategy hashStrategy, - final KType... elements) { - final KTypeCustomHashSet set = new KTypeCustomHashSet(elements.length, hashStrategy); - set.add(elements); - return set; - } - - /** - * Create a set from elements of another container. - */ - public static KTypeCustomHashSet from(final KTypeContainer container, - final KTypeHashingStrategy hashStrategy) { - return new KTypeCustomHashSet(container, hashStrategy); - } - - /** - * Create a new hash set with default parameters (shortcut - * instead of using a constructor). - */ - public static KTypeCustomHashSet newInstance(final KTypeHashingStrategy hashStrategy) { - return new KTypeCustomHashSet(hashStrategy); - } - - /** - * Returns a new object of this class with no need to declare generic type - * (shortcut instead of using a constructor). - */ - public static KTypeCustomHashSet newInstance(final int initialCapacity, final double loadFactor, - final KTypeHashingStrategy hashStrategy) { - return new KTypeCustomHashSet(initialCapacity, loadFactor, hashStrategy); - } - - /** - * Returns the current hashing strategy in use. - */ - public KTypeHashingStrategy strategy() { - return this.hashStrategy; - } - - //Test for existence in template - /*! #if ($TemplateOptions.declareInline("is_allocated(slot, keys)", - "<*>==>!Intrinsics.isEmpty(keys[slot])")) !*/ - /** - * template version - * (actual method is inlined in generated code) - */ - private boolean is_allocated(final int slot, final KType[] keys) { - - return !Intrinsics. isEmpty(keys[slot]); - } - - /*! #end !*/ - - /*! #if ($TemplateOptions.declareInline("probe_distance(slot, cached)", - "<*>==>slot < cached[slot] ? slot + cached.length - cached[slot] : slot - cached[slot]")) !*/ - /** - * (actual method is inlined in generated code) - */ - private int probe_distance(final int slot, final int[] cached) { - - final int rh = cached[slot]; - - /*! #if($DEBUG) !*/ - //Check : cached hashed slot is == computed value - final int mask = cached.length - 1; - assert rh == (REHASH(this.hashStrategy, Intrinsics. cast(this.keys[slot])) & mask); - /*! #end !*/ - - if (slot < rh) { - //wrap around - return slot + cached.length - rh; - } - - return slot - rh; - } - - /*! #end !*/ - - /*! #if ($TemplateOptions.declareInline("REHASH(strategy, value)", - "<*>==>BitMixer.mix(strategy.computeHashCode(value) , this.perturbation )")) !*/ - /** - * (inlined in generated code) - */ - private int REHASH(final KTypeHashingStrategy strategy, final KType value) { - - return BitMixer.mix(strategy.computeHashCode(value), this.perturbation); - } - - /*! #end !*/ - - /*! #if ($TemplateOptions.declareInline("REHASH2(strategy, value, perturb)", - "<*>==>BitMixer.mix(strategy.computeHashCode(value) , perturb)")) !*/ - /** - * REHASH2 method for rehashing the keys with perturbation seed as parameter - * (inlined in generated code) - * Thanks to single array mode, no need to check for null/0 or booleans. - */ - private int REHASH2(final KTypeHashingStrategy strategy, final KType value, final int perturb) { - - return BitMixer.mix(strategy.computeHashCode(value), perturb); - } - /*! #end !*/ -} diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeHashSet.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeHashSet.java index c31030a88..fe5282a62 100644 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeHashSet.java +++ b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeHashSet.java @@ -16,6 +16,14 @@ * A hash set of KTypes, implemented using using open * addressing with linear probing for collision resolution. * +#if ($TemplateOptions.KTypeGeneric) + *

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. + *

+#end + * *

* The internal buffers of this implementation ({@link #keys}, etc...) * are always allocated to the nearest size that is a power of two. When @@ -101,6 +109,45 @@ public class KTypeHashSet */ private final int perturbation = Containers.randomSeed32(); + /*! #if ($TemplateOptions.KTypeGeneric) !*/ + + /** + * 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 KType 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 KType to be compared + * @param b not-null KType 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 Intrinsics. equalsNotNull(a, b); + } + + /*! #end !*/ + /** * Default constructor: Creates a hash set with the default capacity of {@link Containers#DEFAULT_EXPECTED_ELEMENTS}, * load factor of {@link HashContainers#DEFAULT_LOAD_FACTOR}. @@ -169,7 +216,7 @@ public boolean add(KType e) { /*! #end !*/ while (!Intrinsics. isEmpty(existing = keys[slot])) { - if (Intrinsics. equalsNotNull(e, existing)) { + if (KEYEQUALS(e, existing)) { return false; } @@ -461,7 +508,7 @@ public boolean remove(final KType key) { while (!Intrinsics. isEmpty(existing = keys[slot]) /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - if (Intrinsics. equalsNotNull(key, existing)) { + if (KEYEQUALS(key, existing)) { shiftConflictingKeys(slot); return true; @@ -567,7 +614,7 @@ public boolean contains(final KType key) { while (!Intrinsics. isEmpty(existing = keys[slot]) /*! #if ($RH) !*/&& dist <= probe_distance(slot, cached) /*! #end !*/) { - if (Intrinsics. equalsNotNull(key, existing)) { + if (KEYEQUALS(key, existing)) { return true; } slot = (slot + 1) & mask; @@ -955,7 +1002,7 @@ private int probe_distance(final int slot, final int[] cached) { /*! #end !*/ /*! #if ($TemplateOptions.declareInline("REHASH(value)", - "==>BitMixer.mix(value.hashCode() , this.perturbation)", + "==>BitMixer.mix(hashKey(value) , this.perturbation)", "<*>==>BitMixer.mix(value , this.perturbation)")) !*/ /** * REHASH method for rehashing the keys. @@ -964,13 +1011,13 @@ private int probe_distance(final int slot, final int[] cached) { */ private int REHASH(final KType value) { - return BitMixer.mix(value.hashCode(), this.perturbation); + return BitMixer.mix(hashKey(value), this.perturbation); } /*! #end !*/ /*! #if ($TemplateOptions.declareInline("REHASH2(value, perturb)", - "==>BitMixer.mix(value.hashCode() , perturb)", + "==>BitMixer.mix(hashKey(value) , perturb)", "<*>==>BitMixer.mix(value , perturb)")) !*/ /** * REHASH2 method for rehashing the keys with perturbation seed as parameter @@ -979,7 +1026,19 @@ private int REHASH(final KType value) { */ private int REHASH2(final KType value, final int perturb) { - return BitMixer.mix(value.hashCode(), perturb); + return BitMixer.mix(hashKey(value), perturb); + } + /*! #end !*/ + + /*! #if ($TemplateOptions.declareInline("KEYEQUALS(key1, key2)", + "==>equalKeys(key1, key2)", + "<*>==>Intrinsics. equalsNotNull(key1, key2)")) !*/ + /** + * macro which hides the applied equality criteria + */ + private boolean KEYEQUALS(final KType key1, final KType key2) { + + return equalKeys(key1, key2); } /*! #end !*/ } diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeIdentityHashSet.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeIdentityHashSet.java index 3ab2b4da6..24cdc9f95 100644 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeIdentityHashSet.java +++ b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/sets/KTypeIdentityHashSet.java @@ -10,16 +10,33 @@ * * The difference with {@link KTypeHashSet} is that it uses direct Object reference equality for comparison and * direct "address" {@link System#identityHashCode(Object)} for hashCode(), instead of using - * the built-in hashCode() / equals(). + * the built-in {@link #hashCode()} / {@link #equals(Object)}. * *

This implementation supports null keys.

* */ /*! ${TemplateOptions.generatedAnnotation} !*/ public final class KTypeIdentityHashSet - extends KTypeCustomHashSet +extends KTypeHashSet { - private static final KTypeIdentityHash IDENTITY_EQUALITY = new KTypeIdentityHash(); + /** + * Hash customization to only consider the identity hash code. + */ + @Override + protected final int hashKey(final KType key) { + + return System.identityHashCode(key); + } + + /** + * Equality customization to only consider object identity, comparing + * instances directly by ==. + */ + @Override + protected final boolean equalKeys(final KType a, final KType b) { + + return (a == b); + } /** * Creates a hash set with the default capacity of {@link Containers#DEFAULT_EXPECTED_ELEMENTS}, @@ -42,10 +59,9 @@ public KTypeIdentityHashSet(final int initialCapacity) /** * Creates a hash set with the given capacity and load factor. */ - @SuppressWarnings({ "cast", "unchecked" }) public KTypeIdentityHashSet(final int initialCapacity, final double loadFactor) { - super(initialCapacity, loadFactor, (KTypeIdentityHash) KTypeIdentityHashSet.IDENTITY_EQUALITY); + super(initialCapacity, loadFactor); } /** @@ -106,40 +122,4 @@ public static KTypeIdentityHashSet newInstance(final int initialC { return new KTypeIdentityHashSet(initialCapacity, loadFactor); } - - /** - * Inherited from KTypeOpenCustomHashSet, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static KTypeIdentityHashSet from(final KTypeHashingStrategy hashStrategy, final KType... elements) - { - throw new RuntimeException("Identity hash from(strategy, ...elements) usage logical error"); - } - - /** - * Inherited from KTypeOpenCustomHashSet, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static final KTypeIdentityHashSet from(final KTypeContainer container, final KTypeHashingStrategy hashStrategy) - { - throw new RuntimeException("Identity hash from(KTypeContainer, strategy) usage logical error"); - } - - /** - * Inherited from KTypeOpenCustomHashSet, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static final KTypeIdentityHashSet newInstance(final KTypeHashingStrategy hashStrategy) - { - throw new RuntimeException("Identity hash newInstance(strategy) usage logical error"); - } - - /** - * Inherited from KTypeOpenCustomHashSet, DO NOT USE, throws RuntimeException - * @throws RuntimeException - */ - public static final KTypeIdentityHashSet newInstance(final int initialCapacity, final double loadFactor, final KTypeHashingStrategy hashStrategy) - { - throw new RuntimeException("Identity hash newInstance(strategy) usage logical error"); - } } diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeHashingStrategy.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeHashingStrategy.java deleted file mode 100644 index 9b76ae1fe..000000000 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeHashingStrategy.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.carrotsearch.hppcrt.strategies; - -/*! ${TemplateOptions.doNotGenerateKType("byte", "char", "short", "float", "double" )} !*/ -/** - * Interface to support custom hashing strategies in Hash containers using KTypes - * keys, as replacement of the original equals() and hashCode() methods. - * - */ -/*! ${TemplateOptions.generatedAnnotation} !*/ -public interface KTypeHashingStrategy -{ - - /** - * Compute the hash code for the specific object. - * #if ($TemplateOptions.KTypeGeneric) - * Handling of null objects is up to the implementation. - * #end - * @param object - * @return the computed hash value. - */ - int computeHashCode(KType object); - - /** - * Compares the Object o1 and o2 for equality. - * #if ($TemplateOptions.KTypeGeneric) - * Handling of null objects is up to the implementation. - * #end - * @param o1 - * @param o2 - * @return true for equality. - */ - boolean equals(KType o1, KType o2); -} diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeIdentityHash.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeIdentityHash.java deleted file mode 100644 index 7ae6d6dae..000000000 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeIdentityHash.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.carrotsearch.hppcrt.strategies; - -/*! ${TemplateOptions.doNotGenerateKType("BYTE","CHAR","SHORT","INT","LONG","FLOAT", "DOUBLE")} !*/ -/** - * Standard {@link KTypeHashingStrategy} providing an 'identity' - * behavior for objects, where they are compared by reference, and their - * hashCode() is the 'native' one obtained by {@link System#identityHashCode(Object)} - */ -/*! ${TemplateOptions.generatedAnnotation} !*/ -public final class KTypeIdentityHash implements KTypeHashingStrategy -{ - public KTypeIdentityHash() { - // nothing - } - - @Override - public int computeHashCode(final KType object) { - - return System.identityHashCode(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return o1 == o2; - } - - @Override - public boolean equals(final Object o) { - - if (o instanceof KTypeIdentityHash) { - - return true; - } - - return false; - } - - @Override - public int hashCode() { - - return KTypeIdentityHash.class.hashCode(); - } -} diff --git a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeStandardHash.java b/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeStandardHash.java deleted file mode 100644 index 6f29bdbb2..000000000 --- a/hppcrt/src/main/templates/com/carrotsearch/hppcrt/strategies/KTypeStandardHash.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.carrotsearch.hppcrt.strategies; - -import com.carrotsearch.hppcrt.*; -import com.carrotsearch.hppcrt.hash.BitMixer; - -/*! #import("com/carrotsearch/hppcrt/Intrinsics.java") !*/ -/*! ${TemplateOptions.doNotGenerateKType("byte", "char", "short", "float", "double" )} !*/ -/** - * Standard {@link KTypeHashingStrategy} for KTypes, providing the same behavior as equals()/ hashCode() - * gives for objects. - */ -/*! ${TemplateOptions.generatedAnnotation} !*/ -public final class KTypeStandardHash implements KTypeHashingStrategy -{ - - public KTypeStandardHash() { - // nothing - } - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - - @Override - public boolean equals(final Object o) { - - if (o instanceof KTypeStandardHash) { - - return true; - } - - return false; - } - - @Override - public int hashCode() { - - return System.identityHashCode(KTypeStandardHash.class); - } -} diff --git a/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeCustomHashMapTest.java b/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeCustomHashMapTest.java index 3928b14d7..77d7bb269 100644 --- a/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeCustomHashMapTest.java +++ b/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeCustomHashMapTest.java @@ -10,55 +10,124 @@ /*! #import("com/carrotsearch/hppcrt/Intrinsics.java") !*/ /** - * Tests for {@link KTypeVTypeCustomHashMap}. + * Tests for {@link KTypeVTypeHashMap}. */ -/*! ${TemplateOptions.doNotGenerateKType("byte", "char", "short", "float", "double" )} !*/ +/*! ${TemplateOptions.doNotGenerateKType("byte", "char", "short", "int", "long", "float", "double" )} !*/ /*! ${TemplateOptions.generatedAnnotation} !*/ public class KTypeVTypeCustomHashMapTest extends AbstractKTypeVTypeHashMapTest { private static final int STRIDE = 13; - protected final KTypeHashingStrategy TEST_STRATEGY = new KTypeHashingStrategy() { + // Define a specific way for equivalence: + protected int specialHashCode(final KType object) { + + return BitMixer.mix(cast(castType(object) + KTypeVTypeCustomHashMapTest.STRIDE)); + } + + protected boolean specialEquals(final KType o1, final KType o2) { + + return Intrinsics. equals(cast(castType(o1) + KTypeVTypeCustomHashMapTest.STRIDE), cast(castType(o2) + KTypeVTypeCustomHashMapTest.STRIDE)); + } + + /** + * Define a customized HashMapType + */ + public class KTypeVTypeCustomizedHashMap extends KTypeVTypeHashMap + { @Override - public int computeHashCode(final KType object) { + protected int hashKey(final KType object) { return BitMixer.mix(cast(castType(object) + KTypeVTypeCustomHashMapTest.STRIDE)); } @Override - public boolean equals(final KType o1, final KType o2) { + protected boolean equalKeys(final KType o1, final KType o2) { + + return Intrinsics. equals(cast(castType(o1) + KTypeVTypeCustomHashMapTest.STRIDE), cast(castType(o2) + KTypeVTypeCustomHashMapTest.STRIDE)); + } + + /** + * Re-define constructors. + */ + public KTypeVTypeCustomizedHashMap() { + super(); + } + + /** + * 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 KTypeVTypeCustomizedHashMap(final int initialCapacity) { + super(initialCapacity); + } + + /** + * Creates a hash map with the given initial capacity, + * load factor. + * + * @param loadFactor The load factor (greater than zero and smaller than 1). + */ + public KTypeVTypeCustomizedHashMap(final int initialCapacity, final double loadFactor) { + super(initialCapacity, loadFactor); + + } + + /** + * copy constructor + */ + public KTypeVTypeCustomizedHashMap(final KTypeVTypeAssociativeContainer container) { + super(container); + + } + + @Override + public KTypeVTypeCustomizedHashMap clone() { + + //clone to size() to prevent some cases of exponential sizes, + final KTypeVTypeCustomizedHashMap cloned = new KTypeVTypeCustomizedHashMap(size(), this.loadFactor); + + //We must NOT clone because of independent perturbations seeds + cloned.putAll(this); - return Intrinsics. equals(cast(castType(o1) + KTypeVTypeCustomHashMapTest.STRIDE), cast(castType(o2) - + KTypeVTypeCustomHashMapTest.STRIDE)); + cloned.allocatedDefaultKeyValue = this.allocatedDefaultKeyValue; + cloned.allocatedDefaultKey = this.allocatedDefaultKey; + cloned.defaultValue = this.defaultValue; + + return cloned; } - }; + }//end class KTypeVTypeCustomizedHashMap @Override protected KTypeVTypeMap createNewMapInstance(final int initialCapacity, final double loadFactor) { - return new KTypeVTypeCustomHashMap(initialCapacity, loadFactor, this.TEST_STRATEGY); + return new KTypeVTypeCustomizedHashMap(initialCapacity, loadFactor); } @Override protected KType[] getKeys(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); + final KTypeVTypeCustomizedHashMap concreteClass = (KTypeVTypeCustomizedHashMap) (testMap); return Intrinsics. cast(concreteClass.keys); } @Override protected VType[] getValues(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); + final KTypeVTypeCustomizedHashMap concreteClass = (KTypeVTypeCustomizedHashMap) (testMap); return Intrinsics. cast(concreteClass.values); } @Override protected boolean isAllocatedDefaultKey(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); + final KTypeVTypeCustomizedHashMap concreteClass = (KTypeVTypeCustomizedHashMap) (testMap); return concreteClass.allocatedDefaultKey; @@ -66,43 +135,51 @@ protected boolean isAllocatedDefaultKey(final KTypeVTypeMap testMa @Override protected VType getAllocatedDefaultKeyValue(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); + final KTypeVTypeCustomizedHashMap concreteClass = (KTypeVTypeCustomizedHashMap) (testMap); return concreteClass.allocatedDefaultKeyValue; } @Override protected KTypeVTypeMap getClone(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); + final KTypeVTypeCustomizedHashMap concreteClass = (KTypeVTypeCustomizedHashMap) (testMap); return concreteClass.clone(); } @Override protected KTypeVTypeMap getFrom(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); - return KTypeVTypeCustomHashMap.from(concreteClass, concreteClass.hashStrategy); + //use the copy constructor of the specialized type + return new KTypeVTypeCustomizedHashMap(testMap); } @Override protected KTypeVTypeMap getFromArrays(final KType[] keys, final VType[] values) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (this.map); - return KTypeVTypeCustomHashMap.from(Intrinsics. cast(keys), - Intrinsics. cast(values), concreteClass.hashStrategy); + Assert.assertEquals(keys.length, values.length); + + //cannot use the original from(), so use the copy constructor and push explicitly. + final KTypeVTypeCustomizedHashMap returnedInstance = new KTypeVTypeCustomizedHashMap(); + + for (int i = 0; i < keys.length; i++) { + + returnedInstance.put(keys[i], values[i]); + } + + return returnedInstance; } @Override protected KTypeVTypeMap getCopyConstructor(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); - return new KTypeVTypeCustomHashMap(concreteClass, concreteClass.hashStrategy); + //use the copy constructor + return new KTypeVTypeCustomizedHashMap(testMap); } @Override int getEntryPoolSize(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); + final KTypeVTypeHashMap concreteClass = (KTypeVTypeHashMap) (testMap); return concreteClass.entryIteratorPool.size(); } @@ -110,35 +187,35 @@ int getEntryPoolSize(final KTypeVTypeMap testMap) { @Override int getKeysPoolSize(final KTypeCollection keys) { - final KTypeVTypeCustomHashMap.KeysCollection concreteClass = (KTypeVTypeCustomHashMap.KeysCollection) (keys); + final KTypeVTypeHashMap.KeysCollection concreteClass = (KTypeVTypeHashMap.KeysCollection) (keys); return concreteClass.keyIteratorPool.size(); } @Override int getValuesPoolSize(final KTypeCollection values) { - final KTypeVTypeCustomHashMap.ValuesCollection concreteClass = (KTypeVTypeCustomHashMap.ValuesCollection) (values); + final KTypeVTypeHashMap.ValuesCollection concreteClass = (KTypeVTypeHashMap.ValuesCollection) (values); return concreteClass.valuesIteratorPool.size(); } @Override int getEntryPoolCapacity(final KTypeVTypeMap testMap) { - final KTypeVTypeCustomHashMap concreteClass = (KTypeVTypeCustomHashMap) (testMap); + final KTypeVTypeHashMap concreteClass = (KTypeVTypeHashMap) (testMap); return concreteClass.entryIteratorPool.capacity(); } @Override int getKeysPoolCapacity(final KTypeCollection keys) { - final KTypeVTypeCustomHashMap.KeysCollection concreteClass = (KTypeVTypeCustomHashMap.KeysCollection) (keys); + final KTypeVTypeHashMap.KeysCollection concreteClass = (KTypeVTypeHashMap.KeysCollection) (keys); return concreteClass.keyIteratorPool.capacity(); } @Override int getValuesPoolCapacity(final KTypeCollection values) { - final KTypeVTypeCustomHashMap.ValuesCollection concreteClass = (KTypeVTypeCustomHashMap.ValuesCollection) (values); + final KTypeVTypeHashMap.ValuesCollection concreteClass = (KTypeVTypeHashMap.ValuesCollection) (values); return concreteClass.valuesIteratorPool.capacity(); } @@ -149,28 +226,22 @@ int getValuesPoolCapacity(final KTypeCollection values) { // @Test - public void testHashingStrategyCloneEquals() { + public void testCustomizedCloneEquals() { //a) Check that 2 different sets filled the same way with same values and equal strategies. //are indeed equal. final long TEST_SEED = 23167132166456L; final int TEST_SIZE = (int) 100e3; - final KTypeStandardHash std1 = new KTypeStandardHash(); - final KTypeVTypeCustomHashMap refMap = createMapWithRandomData(TEST_SIZE, std1, TEST_SEED); - KTypeVTypeCustomHashMap refMap2 = createMapWithRandomData(TEST_SIZE, std1, TEST_SEED); + final KTypeVTypeHashMap refMap = createCustomizedMapWithRandomData(TEST_SIZE, TEST_SEED); + KTypeVTypeHashMap refMap2 = createCustomizedMapWithRandomData(TEST_SIZE, TEST_SEED); + //Both are constructed with the same KTypeVTypeCustomizedHashMap type, should be equals. Assert.assertEquals(refMap, refMap2); //b) Clone the above. All sets are now identical. - KTypeVTypeCustomHashMap refMapclone = refMap.clone(); - KTypeVTypeCustomHashMap refMap2clone = refMap2.clone(); - - //all strategies are null - Assert.assertEquals(refMap.strategy(), refMap2.strategy()); - Assert.assertEquals(refMap2.strategy(), refMapclone.strategy()); - Assert.assertEquals(refMapclone.strategy(), refMap2clone.strategy()); - Assert.assertEquals(refMap2clone.strategy(), std1); + KTypeVTypeHashMap refMapclone = refMap.clone(); + KTypeVTypeHashMap refMap2clone = refMap2.clone(); Assert.assertEquals(refMap, refMapclone); Assert.assertEquals(refMapclone, refMap2); @@ -183,150 +254,44 @@ public void testHashingStrategyCloneEquals() { refMap2clone = null; System.gc(); - //c) Create a set nb 3 with same integer content, but with a strategy mapping on equals. - final KTypeVTypeCustomHashMap refMap3 = createMapWithRandomData(TEST_SIZE, - new KTypeHashingStrategy() { - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - }, TEST_SEED); - - //because they do the same thing as above, but with semantically different strategies, ref3 is != ref - Assert.assertFalse(refMap.equals(refMap3)); - - //However, if we cloned refMap3 - final KTypeVTypeCustomHashMap refMap3clone = refMap3.clone(); - Assert.assertEquals(refMap3, refMap3clone); - - //strategies are copied by reference only - Assert.assertTrue(refMap3.strategy() == refMap3clone.strategy()); - - //d) Create identical set with same different strategy instances, but which consider themselves equals() - KTypeVTypeCustomHashMap refMap4 = createMapWithRandomData(TEST_SIZE, - new KTypeHashingStrategy() { - - @Override - public boolean equals(final Object obj) { - - return true; - } - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - }, TEST_SEED); - - KTypeVTypeCustomHashMap refMap4Image = createMapWithRandomData(TEST_SIZE, - new KTypeHashingStrategy() { - - @Override - public boolean equals(final Object obj) { - - return true; - } - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - }, TEST_SEED); - - Assert.assertEquals(refMap4, refMap4Image); - //but strategy instances are indeed 2 different objects - Assert.assertFalse(refMap4.strategy() == refMap4Image.strategy()); - - //cleanup - refMap4 = null; - refMap4Image = null; - System.gc(); - - //e) Do contrary to 4), hashStrategies always != from each other by equals. - final KTypeHashingStrategy alwaysDifferentStrategy = new KTypeHashingStrategy() { + //c) Create a refMap3 with same equivalence logic, but they are not equal because one + // is KTypeVTypeCustomizedHashMap, the other another anonymous type: - @Override - public boolean equals(final Object obj) { - - //never equal !!! - return false; - } + final KTypeVTypeHashMap refMap3 = new KTypeVTypeHashMap() { @Override - public int computeHashCode(final KType object) { + protected int hashKey(final KType key) { - return BitMixer.mix(object); + return specialHashCode(key); } @Override - public boolean equals(final KType o1, final KType o2) { + protected boolean equalKeys(final KType a, final KType b) { - return Intrinsics. equals(o1, o2); + return specialEquals(a, b); } }; - final KTypeVTypeCustomHashMap refMap5 = createMapWithRandomData(TEST_SIZE, alwaysDifferentStrategy, - TEST_SEED); - final KTypeVTypeCustomHashMap refMap5alwaysDifferent = createMapWithRandomData(TEST_SIZE, - alwaysDifferentStrategy, TEST_SEED); + Assert.assertFalse(refMap.equals(refMap3)); + Assert.assertFalse(refMap3.equals(refMap)); - //both sets are NOT equal because their strategies said they are different - Assert.assertFalse(refMap5.equals(refMap5alwaysDifferent)); + //Clone the anonymous type : all types are different ! + final KTypeVTypeHashMap refMap3Cloned = refMap3.clone(); + + Assert.assertFalse(refMap3Cloned.equals(refMap3)); + Assert.assertFalse(refMap3.equals(refMap3Cloned)); } @Test - public void testHashingStrategyAddContainsRemove() { + public void testCustomizedAddContainsRemove() { + final long TEST_SEED = 749741621030146103L; final int TEST_SIZE = (int) 500e3; - //those following 3 sets behave indeed the same in the test context: - final KTypeVTypeCustomHashMap refMap = KTypeVTypeCustomHashMap - .newInstance(new KTypeStandardHash()); - - final KTypeVTypeCustomHashMap refMapIdenticalStrategy = KTypeVTypeCustomHashMap.newInstance( - Containers.DEFAULT_EXPECTED_ELEMENTS, HashContainers.DEFAULT_LOAD_FACTOR, new KTypeHashingStrategy() { - - @Override - public boolean equals(final Object obj) { - - //always - return true; - } - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { + //those following 3 maps behave indeed the same in the test context: + final KTypeVTypeCustomizedHashMap refMap = new KTypeVTypeCustomizedHashMap(); - return Intrinsics. equals(o1, o2); - } - }); + final KTypeVTypeCustomizedHashMap refMapSameType = new KTypeVTypeCustomizedHashMap(); //compute the iterations doing multiple operations final Random prng = new Random(TEST_SEED); @@ -336,34 +301,32 @@ public boolean equals(final KType o1, final KType o2) { int putValue = prng.nextInt(); refMap.put(cast(putValue), vcast(putValue)); - refMapIdenticalStrategy.put(cast(putValue), vcast(putValue)); + refMapSameType.put(cast(putValue), vcast(putValue)); - Assert.assertEquals(refMap.containsKey(cast(putValue)), refMapIdenticalStrategy.containsKey(cast(putValue))); + Assert.assertEquals(refMap.containsKey(cast(putValue)), refMapSameType.containsKey(cast(putValue))); final boolean isToBeRemoved = (prng.nextInt() % 3 == 0); putValue = prng.nextInt(); if (isToBeRemoved) { refMap.remove(cast(putValue)); - refMapIdenticalStrategy.remove(cast(putValue)); + refMapSameType.remove(cast(putValue)); Assert.assertFalse(refMap.containsKey(cast(putValue))); - Assert.assertFalse(refMapIdenticalStrategy.containsKey(cast(putValue))); + Assert.assertFalse(refMapSameType.containsKey(cast(putValue))); } - Assert.assertEquals(refMap.containsKey(cast(putValue)), refMapIdenticalStrategy.containsKey(cast(putValue))); + Assert.assertEquals(refMap.containsKey(cast(putValue)), refMapSameType.containsKey(cast(putValue))); //test size - Assert.assertEquals(refMap.size(), refMapIdenticalStrategy.size()); + Assert.assertEquals(refMap.size(), refMapSameType.size()); } } - private KTypeVTypeCustomHashMap createMapWithRandomData(final int size, - final KTypeHashingStrategy strategy, final long randomSeed) { + private KTypeVTypeCustomizedHashMap createCustomizedMapWithRandomData(final int size, final long randomSeed) { final Random prng = new Random(randomSeed); - final KTypeVTypeCustomHashMap newMap = KTypeVTypeCustomHashMap.newInstance( - Containers.DEFAULT_EXPECTED_ELEMENTS, HashContainers.DEFAULT_LOAD_FACTOR, strategy); + final KTypeVTypeCustomizedHashMap newMap = new KTypeVTypeCustomizedHashMap(); for (int i = 0; i < size; i++) { newMap.put(cast(prng.nextInt()), vcast(prng.nextInt())); diff --git a/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMapTest.java b/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMapTest.java index 61b8b3d54..39b3a7f8d 100644 --- a/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMapTest.java +++ b/hppcrt/src/test/templates/com/carrotsearch/hppcrt/maps/KTypeVTypeHashMapTest.java @@ -71,7 +71,6 @@ protected KTypeVTypeMap getFrom(final KTypeVTypeMap @Override protected KTypeVTypeMap getFromArrays(final KType[] keys, final VType[] values) { - final KTypeVTypeHashMap concreteClass = (KTypeVTypeHashMap) (this.map); return KTypeVTypeHashMap.from(Intrinsics. cast(keys), Intrinsics. cast(values)); diff --git a/hppcrt/src/test/templates/com/carrotsearch/hppcrt/sets/KTypeCustomHashSetTest.java b/hppcrt/src/test/templates/com/carrotsearch/hppcrt/sets/KTypeCustomHashSetTest.java index 4b7f9b56e..e8b9754c5 100644 --- a/hppcrt/src/test/templates/com/carrotsearch/hppcrt/sets/KTypeCustomHashSetTest.java +++ b/hppcrt/src/test/templates/com/carrotsearch/hppcrt/sets/KTypeCustomHashSetTest.java @@ -6,83 +6,147 @@ import com.carrotsearch.hppcrt.*; import com.carrotsearch.hppcrt.hash.BitMixer; -import com.carrotsearch.hppcrt.TestUtils; -import com.carrotsearch.hppcrt.strategies.*; /*! #import("com/carrotsearch/hppcrt/Intrinsics.java") !*/ /** - * Unit tests for {@link KTypeCustomHashSet}. + * Unit tests for {@link KTypeHashSet}. */ -/*! ${TemplateOptions.doNotGenerateKType("byte", "char", "short", "float", "double" )} !*/ +/*! ${TemplateOptions.doNotGenerateKType("byte", "char", "short", "int", "long", "float", "double" )} !*/ /*! ${TemplateOptions.generatedAnnotation} !*/ public class KTypeCustomHashSetTest extends AbstractKTypeHashSetTest { private static final int STRIDE = 13; - protected final KTypeHashingStrategy TEST_STRATEGY = new KTypeHashingStrategy() { + // Define a specific way for equivalence: + protected int specialHashCode(final KType object) { + + return BitMixer.mix(cast(castType(object) + KTypeCustomHashSetTest.STRIDE)); + } + + protected boolean specialEquals(final KType o1, final KType o2) { + + return Intrinsics. equals(cast(castType(o1) + KTypeCustomHashSetTest.STRIDE), cast(castType(o2) + KTypeCustomHashSetTest.STRIDE)); + } + + /** + * Define a customized HashSetType + */ + public class KTypeCustomizedHashSet extends KTypeHashSet + { @Override - public int computeHashCode(final KType object) { + protected int hashKey(final KType object) { return BitMixer.mix(cast(castType(object) + KTypeCustomHashSetTest.STRIDE)); } @Override - public boolean equals(final KType o1, final KType o2) { + protected boolean equalKeys(final KType o1, final KType o2) { + + return Intrinsics. equals(cast(castType(o1) + KTypeCustomHashSetTest.STRIDE), cast(castType(o2) + KTypeCustomHashSetTest.STRIDE)); + } + + /** + * Re-define constructors. + */ + public KTypeCustomizedHashSet() { + super(); + } + + public KTypeCustomizedHashSet(final int initialCapacity) { + super(initialCapacity); + } + + public KTypeCustomizedHashSet(final int initialCapacity, final double loadFactor) { + super(initialCapacity, loadFactor); + + } + + public KTypeCustomizedHashSet(final KTypeContainer container) { + super(container); + } + + @Override + public KTypeCustomizedHashSet clone() { + + //clone to size() to prevent eventual exponential growth + final KTypeCustomizedHashSet cloned = new KTypeCustomizedHashSet(size(), this.loadFactor); + + //We must NOT clone, because of the independent perturbation seeds + cloned.addAll(this); + + cloned.allocatedDefaultKey = this.allocatedDefaultKey; - return Intrinsics. equals(cast(castType(o1) + KTypeCustomHashSetTest.STRIDE), cast(castType(o2) - + KTypeCustomHashSetTest.STRIDE)); + return cloned; } - }; + }//end class KTypeCustomizedHashSet @Override protected KTypeSet createNewSetInstance(final int initialCapacity, final double loadFactor) { - return new KTypeCustomHashSet(initialCapacity, loadFactor, this.TEST_STRATEGY); + return new KTypeCustomizedHashSet(initialCapacity, loadFactor); } @Override protected KType[] getKeys(final KTypeSet testSet) { - final KTypeCustomHashSet concreteClass = (KTypeCustomHashSet) (testSet); + + final KTypeCustomizedHashSet concreteClass = (KTypeCustomizedHashSet) (testSet); return Intrinsics. cast(concreteClass.keys); } @Override protected boolean isAllocatedDefaultKey(final KTypeSet testSet) { - final KTypeCustomHashSet concreteClass = (KTypeCustomHashSet) (testSet); + final KTypeCustomizedHashSet concreteClass = (KTypeCustomizedHashSet) (testSet); return concreteClass.allocatedDefaultKey; } @Override protected KTypeSet getClone(final KTypeSet testSet) { - final KTypeCustomHashSet concreteClass = (KTypeCustomHashSet) (testSet); + final KTypeCustomizedHashSet concreteClass = (KTypeCustomizedHashSet) (testSet); return concreteClass.clone(); } @Override protected KTypeSet getFrom(final KTypeContainer container) { - return KTypeCustomHashSet.from(container, this.TEST_STRATEGY); + //use the copy constructor + return new KTypeCustomizedHashSet(container); } @Override protected KTypeSet getFrom(final KType... elements) { - return KTypeCustomHashSet.from(this.TEST_STRATEGY, elements); + //use the copy constructor + final KTypeCustomizedHashSet returnedInstance = new KTypeCustomizedHashSet(); + + for (int i = 0; i < elements.length; i++) { + + returnedInstance.add(elements[i]); + } + + return returnedInstance; } @Override protected KTypeSet getFromArray(final KType[] keys) { - return KTypeCustomHashSet.from(this.TEST_STRATEGY, keys); + //use the copy constructor + final KTypeCustomizedHashSet returnedInstance = new KTypeCustomizedHashSet(); + + for (int i = 0; i < keys.length; i++) { + + returnedInstance.add(keys[i]); + } + + return returnedInstance; } @Override protected void addFromArray(final KTypeSet testSet, final KType... keys) { - final KTypeCustomHashSet concreteClass = (KTypeCustomHashSet) (testSet); + final KTypeCustomizedHashSet concreteClass = (KTypeCustomizedHashSet) (testSet); for (final KType key : keys) { @@ -92,20 +156,19 @@ protected void addFromArray(final KTypeSet testSet, final KType... keys) @Override protected KTypeSet getCopyConstructor(final KTypeSet testSet) { - final KTypeCustomHashSet concreteClass = (KTypeCustomHashSet) (testSet); - return new KTypeCustomHashSet(concreteClass, this.TEST_STRATEGY); + return new KTypeCustomizedHashSet(testSet); } @Override int getEntryPoolSize(final KTypeSet testSet) { - final KTypeCustomHashSet concreteClass = (KTypeCustomHashSet) (testSet); + final KTypeCustomizedHashSet concreteClass = (KTypeCustomizedHashSet) (testSet); return concreteClass.entryIteratorPool.size(); } @Override int getEntryPoolCapacity(final KTypeSet testSet) { - final KTypeCustomHashSet concreteClass = (KTypeCustomHashSet) (testSet); + final KTypeCustomizedHashSet concreteClass = (KTypeCustomizedHashSet) (testSet); return concreteClass.entryIteratorPool.capacity(); } @@ -117,7 +180,7 @@ int getEntryPoolCapacity(final KTypeSet testSet) { @Test public void testAddVarArgs() { - final KTypeCustomHashSet testSet = new KTypeCustomHashSet(this.TEST_STRATEGY); + final KTypeCustomizedHashSet testSet = new KTypeCustomizedHashSet(); testSet.add(this.keyE, this.key1, this.key2, this.key1, this.keyE); Assert.assertEquals(3, testSet.size()); TestUtils.assertSortedListEquals(testSet.toArray(), this.keyE, this.key1, this.key2); @@ -126,7 +189,7 @@ public void testAddVarArgs() /* */ @Test public void testAdd2Elements() { - final KTypeCustomHashSet testSet = new KTypeCustomHashSet(this.TEST_STRATEGY); + final KTypeCustomizedHashSet testSet = new KTypeCustomizedHashSet(); testSet.add(this.key1, this.key1); Assert.assertEquals(1, testSet.size()); @@ -138,28 +201,21 @@ public void testAdd2Elements() { // @Test - public void testHashingStrategyCloneEquals() { + public void testCustomizedCloneEquals() { //a) Check that 2 different sets filled the same way with same values and equal strategies. //are indeed equal. final long TEST_SEED = 23167132166456L; final int TEST_SIZE = (int) 100e3; - final KTypeStandardHash std1 = new KTypeStandardHash(); - final KTypeCustomHashSet refSet = createSetWithRandomData(TEST_SIZE, std1, TEST_SEED); - KTypeCustomHashSet refSet2 = createSetWithRandomData(TEST_SIZE, std1, TEST_SEED); + final KTypeHashSet refSet = createCustomizedMapWithRandomData(TEST_SIZE, TEST_SEED); + KTypeHashSet refSet2 = createCustomizedMapWithRandomData(TEST_SIZE, TEST_SEED); Assert.assertEquals(refSet, refSet2); //b) Clone the above. All sets are now identical. - KTypeCustomHashSet refSetclone = refSet.clone(); - KTypeCustomHashSet refSet2clone = refSet2.clone(); - - //all strategies are null - Assert.assertEquals(refSet.strategy(), refSet2.strategy()); - Assert.assertEquals(refSet2.strategy(), refSetclone.strategy()); - Assert.assertEquals(refSetclone.strategy(), refSet2clone.strategy()); - Assert.assertEquals(refSet2clone.strategy(), std1); + KTypeHashSet refSetclone = refSet.clone(); + KTypeHashSet refSet2clone = refSet2.clone(); Assert.assertEquals(refSet, refSetclone); Assert.assertEquals(refSetclone, refSet2); @@ -172,145 +228,43 @@ public void testHashingStrategyCloneEquals() { refSet2clone = null; System.gc(); - //c) Create a set nb 3 with same integer content, but with a strategy mapping on equals. - final KTypeCustomHashSet refSet3 = createSetWithRandomData(TEST_SIZE, new KTypeHashingStrategy() { + //c) Create a set nb 3 with same equivalence logic, but they are not equal because one + // is KTypeCustomizedHashSet, the other another anonymous type: + final KTypeHashSet refSet3 = new KTypeHashSet() { @Override - public int computeHashCode(final KType object) { + protected int hashKey(final KType key) { - return BitMixer.mix(object); + return specialHashCode(key); } @Override - public boolean equals(final KType o1, final KType o2) { + protected boolean equalKeys(final KType a, final KType b) { - return Intrinsics. equals(o1, o2); - } - }, TEST_SEED); + return specialEquals(a, b); + }; + }; //because they do the same thing as above, but with semantically different strategies, ref3 is != ref Assert.assertFalse(refSet.equals(refSet3)); + Assert.assertFalse(refSet3.equals(refSet)); - //However, if we cloned refSet3 - final KTypeCustomHashSet refSet3clone = refSet3.clone(); - Assert.assertEquals(refSet3, refSet3clone); - - //strategies are copied by reference only - Assert.assertTrue(refSet3.strategy() == refSet3clone.strategy()); - - //d) Create identical set with same different strategy instances, but which consider themselves equals() - KTypeCustomHashSet refSet4 = createSetWithRandomData(TEST_SIZE, new KTypeHashingStrategy() { - - @Override - public boolean equals(final Object obj) { - - return true; - } - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } + //clone the anonymous type : all types are different ! + final KTypeHashSet refSet3Cloned = refSet3.clone(); - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - }, TEST_SEED); - - KTypeCustomHashSet refSet4Image = createSetWithRandomData(TEST_SIZE, new KTypeHashingStrategy() { - - @Override - public boolean equals(final Object obj) { - - return true; - } - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - }, TEST_SEED); - - Assert.assertEquals(refSet4, refSet4Image); - //but strategy instances are indeed 2 different objects - Assert.assertFalse(refSet4.strategy() == refSet4Image.strategy()); - - //cleanup - refSet4 = null; - refSet4Image = null; - System.gc(); - - //e) Do contrary to 4), hashStrategies always != from each other by equals. - final KTypeHashingStrategy alwaysDifferentStrategy = new KTypeHashingStrategy() { - - @Override - public boolean equals(final Object obj) { - - //never equal !!! - return false; - } - - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - }; - - final KTypeCustomHashSet refSet5 = createSetWithRandomData(TEST_SIZE, alwaysDifferentStrategy, TEST_SEED); - final KTypeCustomHashSet refSet5alwaysDifferent = createSetWithRandomData(TEST_SIZE, - alwaysDifferentStrategy, TEST_SEED); - - //both sets are NOT equal because their strategies said they are different - Assert.assertFalse(refSet5.equals(refSet5alwaysDifferent)); + Assert.assertFalse(refSet3Cloned.equals(refSet3)); + Assert.assertFalse(refSet3.equals(refSet3Cloned)); } @Test - public void testHashingStrategyAddContainsRemove() { + public void testCustomizedAddContainsRemove() { final long TEST_SEED = 749741621030146103L; final int TEST_SIZE = (int) 500e3; //those following 3 sets behave indeed the same in the test context: - final KTypeCustomHashSet refSet = KTypeCustomHashSet.newInstance(new KTypeStandardHash()); - - final KTypeCustomHashSet refSetIdenticalStrategy = KTypeCustomHashSet.newInstance( - Containers.DEFAULT_EXPECTED_ELEMENTS, HashContainers.DEFAULT_LOAD_FACTOR, new KTypeHashingStrategy() { - - @Override - public boolean equals(final Object obj) { - - //always - return true; - } + final KTypeHashSet refSet = new KTypeCustomizedHashSet(); - @Override - public int computeHashCode(final KType object) { - - return BitMixer.mix(object); - } - - @Override - public boolean equals(final KType o1, final KType o2) { - - return Intrinsics. equals(o1, o2); - } - }); + final KTypeHashSet refSetIdenticalStrategy = new KTypeCustomizedHashSet(); //compute the iterations doing multiple operations final Random prng = new Random(TEST_SEED); @@ -342,14 +296,14 @@ public boolean equals(final KType o1, final KType o2) { } } - private KTypeCustomHashSet createSetWithRandomData(final int size, - final KTypeHashingStrategy strategy, final long randomSeed) { + private KTypeCustomizedHashSet createCustomizedMapWithRandomData(final int size, final long randomSeed) { + final Random prng = new Random(randomSeed); - final KTypeCustomHashSet newSet = KTypeCustomHashSet.newInstance(Containers.DEFAULT_EXPECTED_ELEMENTS, - HashContainers.DEFAULT_LOAD_FACTOR, strategy); + final KTypeCustomizedHashSet newSet = new KTypeCustomizedHashSet(); for (int i = 0; i < size; i++) { + newSet.add(cast(prng.nextInt())); } diff --git a/pom.xml b/pom.xml index 5f34ed397..f384dfc87 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ com.github.vsonnier hppcrt-parent - 0.7.2-SNAPSHOT + 0.7.2 pom HPPC-RT (parent POM) @@ -73,7 +73,7 @@ 4.5.1 - 2.1.16 + 2.1.17 2.6.1 3.3 @@ -84,7 +84,7 @@ 2.6 2.4.1 2.7 - 2.18.1 + 2.19 1.4.1 2.10.3