/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.math3.util;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ConcurrentModificationException;
import java.util.NoSuchElementException;

import org.apache.commons.math3.Field;
import org.apache.commons.math3.FieldElement;

Open addressed map from int to FieldElement.

This class provides a dedicated map from integers to FieldElements with a much smaller memory overhead than standard java.util.Map.

This class is not synchronized. The specialized iterators returned by iterator() are fail-fast: they throw a ConcurrentModificationException when they detect the map has been modified during iteration.

Type parameters:
  • <T> – the type of the field elements
Since:2.0
/** * Open addressed map from int to FieldElement. * <p>This class provides a dedicated map from integers to FieldElements with a * much smaller memory overhead than standard <code>java.util.Map</code>.</p> * <p>This class is not synchronized. The specialized iterators returned by * {@link #iterator()} are fail-fast: they throw a * <code>ConcurrentModificationException</code> when they detect the map has been * modified during iteration.</p> * @param <T> the type of the field elements * @since 2.0 */
public class OpenIntToFieldHashMap<T extends FieldElement<T>> implements Serializable {
Status indicator for free table entries.
/** Status indicator for free table entries. */
protected static final byte FREE = 0;
Status indicator for full table entries.
/** Status indicator for full table entries. */
protected static final byte FULL = 1;
Status indicator for removed table entries.
/** Status indicator for removed table entries. */
protected static final byte REMOVED = 2;
Serializable version identifier.
/** Serializable version identifier. */
private static final long serialVersionUID = -9179080286849120720L;
Load factor for the map.
/** Load factor for the map. */
private static final float LOAD_FACTOR = 0.5f;
Default starting size.

This must be a power of two for bit mask to work properly.

/** Default starting size. * <p>This must be a power of two for bit mask to work properly. </p> */
private static final int DEFAULT_EXPECTED_SIZE = 16;
Multiplier for size growth when map fills up.

This must be a power of two for bit mask to work properly.

/** Multiplier for size growth when map fills up. * <p>This must be a power of two for bit mask to work properly. </p> */
private static final int RESIZE_MULTIPLIER = 2;
Number of bits to perturb the index when probing for collision resolution.
/** Number of bits to perturb the index when probing for collision resolution. */
private static final int PERTURB_SHIFT = 5;
Field to which the elements belong.
/** Field to which the elements belong. */
private final Field<T> field;
Keys table.
/** Keys table. */
private int[] keys;
Values table.
/** Values table. */
private T[] values;
States table.
/** States table. */
private byte[] states;
Return value for missing entries.
/** Return value for missing entries. */
private final T missingEntries;
Current size of the map.
/** Current size of the map. */
private int size;
Bit mask for hash values.
/** Bit mask for hash values. */
private int mask;
Modifications count.
/** Modifications count. */
private transient int count;
Build an empty map with default size and using zero for missing entries.
Params:
  • field – field to which the elements belong
/** * Build an empty map with default size and using zero for missing entries. * @param field field to which the elements belong */
public OpenIntToFieldHashMap(final Field<T>field) { this(field, DEFAULT_EXPECTED_SIZE, field.getZero()); }
Build an empty map with default size
Params:
  • field – field to which the elements belong
  • missingEntries – value to return when a missing entry is fetched
/** * Build an empty map with default size * @param field field to which the elements belong * @param missingEntries value to return when a missing entry is fetched */
public OpenIntToFieldHashMap(final Field<T>field, final T missingEntries) { this(field,DEFAULT_EXPECTED_SIZE, missingEntries); }
Build an empty map with specified size and using zero for missing entries.
Params:
  • field – field to which the elements belong
  • expectedSize – expected number of elements in the map
/** * Build an empty map with specified size and using zero for missing entries. * @param field field to which the elements belong * @param expectedSize expected number of elements in the map */
public OpenIntToFieldHashMap(final Field<T> field,final int expectedSize) { this(field,expectedSize, field.getZero()); }
Build an empty map with specified size.
Params:
  • field – field to which the elements belong
  • expectedSize – expected number of elements in the map
  • missingEntries – value to return when a missing entry is fetched
/** * Build an empty map with specified size. * @param field field to which the elements belong * @param expectedSize expected number of elements in the map * @param missingEntries value to return when a missing entry is fetched */
public OpenIntToFieldHashMap(final Field<T> field,final int expectedSize, final T missingEntries) { this.field = field; final int capacity = computeCapacity(expectedSize); keys = new int[capacity]; values = buildArray(capacity); states = new byte[capacity]; this.missingEntries = missingEntries; mask = capacity - 1; }
Copy constructor.
Params:
  • source – map to copy
/** * Copy constructor. * @param source map to copy */
public OpenIntToFieldHashMap(final OpenIntToFieldHashMap<T> source) { field = source.field; final int length = source.keys.length; keys = new int[length]; System.arraycopy(source.keys, 0, keys, 0, length); values = buildArray(length); System.arraycopy(source.values, 0, values, 0, length); states = new byte[length]; System.arraycopy(source.states, 0, states, 0, length); missingEntries = source.missingEntries; size = source.size; mask = source.mask; count = source.count; }
Compute the capacity needed for a given size.
Params:
  • expectedSize – expected size of the map
Returns:capacity to use for the specified size
/** * Compute the capacity needed for a given size. * @param expectedSize expected size of the map * @return capacity to use for the specified size */
private static int computeCapacity(final int expectedSize) { if (expectedSize == 0) { return 1; } final int capacity = (int) FastMath.ceil(expectedSize / LOAD_FACTOR); final int powerOfTwo = Integer.highestOneBit(capacity); if (powerOfTwo == capacity) { return capacity; } return nextPowerOfTwo(capacity); }
Find the smallest power of two greater than the input value
Params:
  • i – input value
Returns:smallest power of two greater than the input value
/** * Find the smallest power of two greater than the input value * @param i input value * @return smallest power of two greater than the input value */
private static int nextPowerOfTwo(final int i) { return Integer.highestOneBit(i) << 1; }
Get the stored value associated with the given key
Params:
  • key – key associated with the data
Returns:data associated with the key
/** * Get the stored value associated with the given key * @param key key associated with the data * @return data associated with the key */
public T get(final int key) { final int hash = hashOf(key); int index = hash & mask; if (containsKey(key, index)) { return values[index]; } if (states[index] == FREE) { return missingEntries; } int j = index; for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) { j = probe(perturb, j); index = j & mask; if (containsKey(key, index)) { return values[index]; } } return missingEntries; }
Check if a value is associated with a key.
Params:
  • key – key to check
Returns:true if a value is associated with key
/** * Check if a value is associated with a key. * @param key key to check * @return true if a value is associated with key */
public boolean containsKey(final int key) { final int hash = hashOf(key); int index = hash & mask; if (containsKey(key, index)) { return true; } if (states[index] == FREE) { return false; } int j = index; for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) { j = probe(perturb, j); index = j & mask; if (containsKey(key, index)) { return true; } } return false; }
Get an iterator over map elements.

The specialized iterators returned are fail-fast: they throw a ConcurrentModificationException when they detect the map has been modified during iteration.

Returns:iterator over the map elements
/** * Get an iterator over map elements. * <p>The specialized iterators returned are fail-fast: they throw a * <code>ConcurrentModificationException</code> when they detect the map * has been modified during iteration.</p> * @return iterator over the map elements */
public Iterator iterator() { return new Iterator(); }
Perturb the hash for starting probing.
Params:
  • hash – initial hash
Returns:perturbed hash
/** * Perturb the hash for starting probing. * @param hash initial hash * @return perturbed hash */
private static int perturb(final int hash) { return hash & 0x7fffffff; }
Find the index at which a key should be inserted
Params:
  • key – key to lookup
Returns:index at which key should be inserted
/** * Find the index at which a key should be inserted * @param key key to lookup * @return index at which key should be inserted */
private int findInsertionIndex(final int key) { return findInsertionIndex(keys, states, key, mask); }
Find the index at which a key should be inserted
Params:
  • keys – keys table
  • states – states table
  • key – key to lookup
  • mask – bit mask for hash values
Returns:index at which key should be inserted
/** * Find the index at which a key should be inserted * @param keys keys table * @param states states table * @param key key to lookup * @param mask bit mask for hash values * @return index at which key should be inserted */
private static int findInsertionIndex(final int[] keys, final byte[] states, final int key, final int mask) { final int hash = hashOf(key); int index = hash & mask; if (states[index] == FREE) { return index; } else if (states[index] == FULL && keys[index] == key) { return changeIndexSign(index); } int perturb = perturb(hash); int j = index; if (states[index] == FULL) { while (true) { j = probe(perturb, j); index = j & mask; perturb >>= PERTURB_SHIFT; if (states[index] != FULL || keys[index] == key) { break; } } } if (states[index] == FREE) { return index; } else if (states[index] == FULL) { // due to the loop exit condition, // if (states[index] == FULL) then keys[index] == key return changeIndexSign(index); } final int firstRemoved = index; while (true) { j = probe(perturb, j); index = j & mask; if (states[index] == FREE) { return firstRemoved; } else if (states[index] == FULL && keys[index] == key) { return changeIndexSign(index); } perturb >>= PERTURB_SHIFT; } }
Compute next probe for collision resolution
Params:
  • perturb – perturbed hash
  • j – previous probe
Returns:next probe
/** * Compute next probe for collision resolution * @param perturb perturbed hash * @param j previous probe * @return next probe */
private static int probe(final int perturb, final int j) { return (j << 2) + j + perturb + 1; }
Change the index sign
Params:
  • index – initial index
Returns:changed index
/** * Change the index sign * @param index initial index * @return changed index */
private static int changeIndexSign(final int index) { return -index - 1; }
Get the number of elements stored in the map.
Returns:number of elements stored in the map
/** * Get the number of elements stored in the map. * @return number of elements stored in the map */
public int size() { return size; }
Remove the value associated with a key.
Params:
  • key – key to which the value is associated
Returns:removed value
/** * Remove the value associated with a key. * @param key key to which the value is associated * @return removed value */
public T remove(final int key) { final int hash = hashOf(key); int index = hash & mask; if (containsKey(key, index)) { return doRemove(index); } if (states[index] == FREE) { return missingEntries; } int j = index; for (int perturb = perturb(hash); states[index] != FREE; perturb >>= PERTURB_SHIFT) { j = probe(perturb, j); index = j & mask; if (containsKey(key, index)) { return doRemove(index); } } return missingEntries; }
Check if the tables contain an element associated with specified key at specified index.
Params:
  • key – key to check
  • index – index to check
Returns:true if an element is associated with key at index
/** * Check if the tables contain an element associated with specified key * at specified index. * @param key key to check * @param index index to check * @return true if an element is associated with key at index */
private boolean containsKey(final int key, final int index) { return (key != 0 || states[index] == FULL) && keys[index] == key; }
Remove an element at specified index.
Params:
  • index – index of the element to remove
Returns:removed value
/** * Remove an element at specified index. * @param index index of the element to remove * @return removed value */
private T doRemove(int index) { keys[index] = 0; states[index] = REMOVED; final T previous = values[index]; values[index] = missingEntries; --size; ++count; return previous; }
Put a value associated with a key in the map.
Params:
  • key – key to which value is associated
  • value – value to put in the map
Returns:previous value associated with the key
/** * Put a value associated with a key in the map. * @param key key to which value is associated * @param value value to put in the map * @return previous value associated with the key */
public T put(final int key, final T value) { int index = findInsertionIndex(key); T previous = missingEntries; boolean newMapping = true; if (index < 0) { index = changeIndexSign(index); previous = values[index]; newMapping = false; } keys[index] = key; states[index] = FULL; values[index] = value; if (newMapping) { ++size; if (shouldGrowTable()) { growTable(); } ++count; } return previous; }
Grow the tables.
/** * Grow the tables. */
private void growTable() { final int oldLength = states.length; final int[] oldKeys = keys; final T[] oldValues = values; final byte[] oldStates = states; final int newLength = RESIZE_MULTIPLIER * oldLength; final int[] newKeys = new int[newLength]; final T[] newValues = buildArray(newLength); final byte[] newStates = new byte[newLength]; final int newMask = newLength - 1; for (int i = 0; i < oldLength; ++i) { if (oldStates[i] == FULL) { final int key = oldKeys[i]; final int index = findInsertionIndex(newKeys, newStates, key, newMask); newKeys[index] = key; newValues[index] = oldValues[i]; newStates[index] = FULL; } } mask = newMask; keys = newKeys; values = newValues; states = newStates; }
Check if tables should grow due to increased size.
Returns:true if tables should grow
/** * Check if tables should grow due to increased size. * @return true if tables should grow */
private boolean shouldGrowTable() { return size > (mask + 1) * LOAD_FACTOR; }
Compute the hash value of a key
Params:
  • key – key to hash
Returns:hash value of the key
/** * Compute the hash value of a key * @param key key to hash * @return hash value of the key */
private static int hashOf(final int key) { final int h = key ^ ((key >>> 20) ^ (key >>> 12)); return h ^ (h >>> 7) ^ (h >>> 4); }
Iterator class for the map.
/** Iterator class for the map. */
public class Iterator {
Reference modification count.
/** Reference modification count. */
private final int referenceCount;
Index of current element.
/** Index of current element. */
private int current;
Index of next element.
/** Index of next element. */
private int next;
Simple constructor.
/** * Simple constructor. */
private Iterator() { // preserve the modification count of the map to detect concurrent modifications later referenceCount = count; // initialize current index next = -1; try { advance(); } catch (NoSuchElementException nsee) { // NOPMD // ignored } }
Check if there is a next element in the map.
Returns:true if there is a next element
/** * Check if there is a next element in the map. * @return true if there is a next element */
public boolean hasNext() { return next >= 0; }
Get the key of current entry.
Throws:
Returns:key of current entry
/** * Get the key of current entry. * @return key of current entry * @exception ConcurrentModificationException if the map is modified during iteration * @exception NoSuchElementException if there is no element left in the map */
public int key() throws ConcurrentModificationException, NoSuchElementException { if (referenceCount != count) { throw new ConcurrentModificationException(); } if (current < 0) { throw new NoSuchElementException(); } return keys[current]; }
Get the value of current entry.
Throws:
Returns:value of current entry
/** * Get the value of current entry. * @return value of current entry * @exception ConcurrentModificationException if the map is modified during iteration * @exception NoSuchElementException if there is no element left in the map */
public T value() throws ConcurrentModificationException, NoSuchElementException { if (referenceCount != count) { throw new ConcurrentModificationException(); } if (current < 0) { throw new NoSuchElementException(); } return values[current]; }
Advance iterator one step further.
Throws:
  • ConcurrentModificationException – if the map is modified during iteration
  • NoSuchElementException – if there is no element left in the map
/** * Advance iterator one step further. * @exception ConcurrentModificationException if the map is modified during iteration * @exception NoSuchElementException if there is no element left in the map */
public void advance() throws ConcurrentModificationException, NoSuchElementException { if (referenceCount != count) { throw new ConcurrentModificationException(); } // advance on step current = next; // prepare next step try { while (states[++next] != FULL) { // NOPMD // nothing to do } } catch (ArrayIndexOutOfBoundsException e) { next = -2; if (current < 0) { throw new NoSuchElementException(); } } } }
Read a serialized object.
Params:
  • stream – input stream
Throws:
/** * Read a serialized object. * @param stream input stream * @throws IOException if object cannot be read * @throws ClassNotFoundException if the class corresponding * to the serialized object cannot be found */
private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); count = 0; }
Build an array of elements.
Params:
  • length – size of the array to build
Returns:a new array
/** Build an array of elements. * @param length size of the array to build * @return a new array */
@SuppressWarnings("unchecked") // field is of type T private T[] buildArray(final int length) { return (T[]) Array.newInstance(field.getRuntimeClass(), length); } }