Added an int/double hash map (OpenIntToDoubleHashMap) with much smaller
memory overhead than standard java.util.Map (open addressing and no boxing) git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@726459 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
139a0e2c53
commit
578a219a0f
3
pom.xml
3
pom.xml
|
@ -123,6 +123,9 @@
|
|||
<contributor>
|
||||
<name>Matthias Hummel</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Ismael Juma</name>
|
||||
</contributor>
|
||||
<contributor>
|
||||
<name>Piotr Kochanski</name>
|
||||
</contributor>
|
||||
|
|
|
@ -22,8 +22,10 @@ import java.io.PrintStream;
|
|||
import java.io.PrintWriter;
|
||||
import java.text.MessageFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
|
@ -35,7 +37,7 @@ import java.util.ResourceBundle;
|
|||
public class MathRuntimeException extends RuntimeException {
|
||||
|
||||
/** Serializable version identifier. */
|
||||
private static final long serialVersionUID = 8560172512507661982L;
|
||||
private static final long serialVersionUID = -143052521750625264L;
|
||||
|
||||
/** Cache for resources bundle. */
|
||||
private static ResourceBundle cachedResources = null;
|
||||
|
@ -307,6 +309,48 @@ public class MathRuntimeException extends RuntimeException {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>ConcurrentModificationException</code> with specified formatted detail message.
|
||||
* Message formatting is delegated to {@link java.text.MessageFormat}.
|
||||
* @param pattern format specifier
|
||||
* @param arguments format arguments
|
||||
*/
|
||||
public static ConcurrentModificationException createConcurrentModificationException(final String pattern,
|
||||
final Object[] arguments) {
|
||||
return new ConcurrentModificationException(buildMessage(pattern, arguments, Locale.US)) {
|
||||
|
||||
/** Serializable version identifier. */
|
||||
private static final long serialVersionUID = 6134247282754009421L;
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getLocalizedMessage() {
|
||||
return buildMessage(pattern, arguments, Locale.getDefault());
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>NoSuchElementException</code> with specified formatted detail message.
|
||||
* Message formatting is delegated to {@link java.text.MessageFormat}.
|
||||
* @param pattern format specifier
|
||||
* @param arguments format arguments
|
||||
*/
|
||||
public static NoSuchElementException createNoSuchElementException(final String pattern,
|
||||
final Object[] arguments) {
|
||||
return new NoSuchElementException(buildMessage(pattern, arguments, Locale.US)) {
|
||||
|
||||
/** Serializable version identifier. */
|
||||
private static final long serialVersionUID = 7304273322489425799L;
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public String getLocalizedMessage() {
|
||||
return buildMessage(pattern, arguments, Locale.getDefault());
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>ParseException</code> with specified
|
||||
* formatted detail message.
|
||||
|
|
|
@ -202,6 +202,12 @@ public class MessagesResources_fr
|
|||
"une matrice doit comporter au moins une colonne" },
|
||||
{ "some rows have length {0} while others have length {1}",
|
||||
"certaines ligne ont une longueur de {0} alors que d''autres ont une longueur de {1}" },
|
||||
{ "{0}x{1} and {2}x{3} matrices are not addition compatible",
|
||||
"les dimensions {0}x{1} et {2}x{3} sont incompatibles pour l'addition matricielle" },
|
||||
{ "{0}x{1} and {2}x{3} matrices are not subtraction compatible",
|
||||
"les dimensions {0}x{1} et {2}x{3} sont incompatibles pour la soustraction matricielle" },
|
||||
{ "{0}x{1} and {2}x{3} matrices are not multiplication compatible",
|
||||
"les dimensions {0}x{1} et {2}x{3} sont incompatibles pour la multiplication matricielle" },
|
||||
|
||||
// org.apache.commons.math.linear.BigMatrixImpl
|
||||
// org.apache.commons.math.linear.RealMatrixImpl
|
||||
|
@ -219,12 +225,6 @@ public class MessagesResources_fr
|
|||
"tableau des indices de lignes s\u00e9lectionn\u00e9es vide" },
|
||||
{ "empty selected column index array",
|
||||
"tableau des indices de colonnes s\u00e9lectionn\u00e9es vide" },
|
||||
{ "{0}x{1} and {2}x{3} matrices are not multiplication compatible",
|
||||
"les dimensions {0}x{1} et {2}x{3} sont incompatibles pour la multiplication matricielle" },
|
||||
{ "{0}x{1} and {2}x{3} matrices are not addition compatible",
|
||||
"les dimensions {0}x{1} et {2}x{3} sont incompatibles pour l'addition matricielle" },
|
||||
{ "{0}x{1} and {2}x{3} matrices are not subtraction compatible",
|
||||
"les dimensions {0}x{1} et {2}x{3} sont incompatibles pour la soustraction matricielle" },
|
||||
|
||||
// org.apache.commons.math.random.EmpiricalDistributionImpl
|
||||
// org.apache.commons.math.random.ValueServer
|
||||
|
@ -331,7 +331,13 @@ public class MessagesResources_fr
|
|||
{ "invalid number of elements {0} (must be positive)",
|
||||
"nombre d''\u00e9l\u00e9ments {0} invalide (doit \u00eatre positif)" },
|
||||
{ "invalid exponent {0} (must be positive)",
|
||||
"exposant {0} invalide (doit \u00eatre positif)" }
|
||||
"exposant {0} invalide (doit \u00eatre positif)" },
|
||||
|
||||
// org.apache.commons.math.util.OpenIntToDoubleHashMap
|
||||
{ "map has been modified while iterating",
|
||||
"la table d''adressage a \u00e9t\u00e9 modifi\u00e9e pendant l''it\u00e9ration" },
|
||||
{ "iterator exhausted",
|
||||
"it\u00e9ration achev\u00e9e" }
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,555 @@
|
|||
/*
|
||||
* 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.math.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.apache.commons.math.MathRuntimeException;
|
||||
|
||||
/**
|
||||
* Open addressed map from int to double.
|
||||
* <p>This class provides a dedicated map from integers to doubles 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>
|
||||
* @version $Revision$ $Date$
|
||||
* @since 2.0
|
||||
*/
|
||||
public class OpenIntToDoubleHashMap implements Serializable {
|
||||
|
||||
/** Serializable version identifier */
|
||||
private static final long serialVersionUID = -3646337053166149105L;
|
||||
|
||||
/** Load factor for the map. */
|
||||
private static final float LOAD_FACTOR = 0.5f;
|
||||
|
||||
/** 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.
|
||||
* <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. */
|
||||
private static final int PERTURB_SHIFT = 5;
|
||||
|
||||
/** Status indicator for free table entries. */
|
||||
protected static final byte FREE = 0;
|
||||
|
||||
/** Status indicator for full table entries. */
|
||||
protected static final byte FULL = 1;
|
||||
|
||||
/** Status indicator for removed table entries. */
|
||||
protected static final byte REMOVED = 2;
|
||||
|
||||
/** Keys table. */
|
||||
private int[] keys;
|
||||
|
||||
/** Values table. */
|
||||
private double[] values;
|
||||
|
||||
/** States table. */
|
||||
private byte[] states;
|
||||
|
||||
/** Current size of the map. */
|
||||
private int size;
|
||||
|
||||
/** Bit mask for hash values. */
|
||||
private int mask;
|
||||
|
||||
/** Modifications count. */
|
||||
private transient int count;
|
||||
|
||||
/**
|
||||
* Build an empty map with default size.
|
||||
*/
|
||||
public OpenIntToDoubleHashMap() {
|
||||
this(DEFAULT_EXPECTED_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an empty map with specified size.
|
||||
* @param expectedSize expected number of elements in the map
|
||||
*/
|
||||
public OpenIntToDoubleHashMap(final int expectedSize) {
|
||||
final int capacity = computeCapacity(expectedSize);
|
||||
keys = new int[capacity];
|
||||
values = new double[capacity];
|
||||
states = new byte[capacity];
|
||||
mask = capacity - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
* @param source map to copy
|
||||
*/
|
||||
public OpenIntToDoubleHashMap(final OpenIntToDoubleHashMap source) {
|
||||
final int length = source.keys.length;
|
||||
keys = new int[length];
|
||||
System.arraycopy(source.keys, 0, keys, 0, length);
|
||||
values = new double[length];
|
||||
System.arraycopy(source.values, 0, values, 0, length);
|
||||
states = new byte[length];
|
||||
System.arraycopy(source.states, 0, states, 0, length);
|
||||
size = source.size;
|
||||
mask = source.mask;
|
||||
count = source.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) Math.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
|
||||
* @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
|
||||
* @param key key associated with the data
|
||||
* @return data associated with the key
|
||||
*/
|
||||
public double 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 0.0;
|
||||
}
|
||||
|
||||
for (int perturb = perturb(hash), j = index; states[index] != FREE; perturb >>= PERTURB_SHIFT) {
|
||||
j = probe(perturb, j);
|
||||
index = j & mask;
|
||||
if (containsKey(key, index)) {
|
||||
return values[index];
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
for (int perturb = perturb(hash), j = index; 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.
|
||||
* <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.
|
||||
* @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
|
||||
* @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
|
||||
* @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
|
||||
* @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
|
||||
* @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.
|
||||
* @return number of elements stored in the map
|
||||
*/
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the value associated with a key.
|
||||
* @param key key to which the value is associated
|
||||
* @return removed value
|
||||
*/
|
||||
public double 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 0.0;
|
||||
}
|
||||
|
||||
for (int perturb = perturb(hash), j = index; states[index] != FREE; perturb >>= PERTURB_SHIFT) {
|
||||
j = probe(perturb, j);
|
||||
index = j & mask;
|
||||
if (containsKey(key, index)) {
|
||||
return doRemove(index);
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param index index of the element to remove
|
||||
* @return removed value
|
||||
*/
|
||||
private double doRemove(int index) {
|
||||
keys[index] = 0;
|
||||
states[index] = REMOVED;
|
||||
final double previous = values[index];
|
||||
values[index] = 0;
|
||||
--size;
|
||||
++count;
|
||||
return previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 double put(final int key, final double value) {
|
||||
int index = findInsertionIndex(key);
|
||||
double previous = 0.0;
|
||||
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.
|
||||
*/
|
||||
private void growTable() {
|
||||
|
||||
final int oldLength = states.length;
|
||||
final int[] oldKeys = keys;
|
||||
final double[] oldValues = values;
|
||||
final byte[] oldStates = states;
|
||||
|
||||
final int newLength = RESIZE_MULTIPLIER * oldLength;
|
||||
final int[] newKeys = new int[newLength];
|
||||
final double[] newValues = new double[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.
|
||||
* @return true if tables should grow
|
||||
*/
|
||||
private boolean shouldGrowTable() {
|
||||
return size > (mask + 1) * LOAD_FACTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. */
|
||||
public class Iterator {
|
||||
|
||||
/** Reference modification count. */
|
||||
final int referenceCount;
|
||||
|
||||
/** Index of next element. */
|
||||
private int index;
|
||||
|
||||
/**
|
||||
* Simple constructor.
|
||||
*/
|
||||
private Iterator() {
|
||||
referenceCount = count;
|
||||
index = -1;
|
||||
goToNext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is a next element in the map.
|
||||
* @return true if there is a next element
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return index >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next entry.
|
||||
* @return next entry
|
||||
* @exception ConcurrentModificationException if the map is modified during iteration
|
||||
* @exception NoSuchElementException if there is no element left in the map
|
||||
*/
|
||||
public Entry next()
|
||||
throws ConcurrentModificationException, NoSuchElementException {
|
||||
if (referenceCount != count) {
|
||||
throw MathRuntimeException.createConcurrentModificationException("map has been modified while iterating",
|
||||
null);
|
||||
}
|
||||
if (index < 0) {
|
||||
throw MathRuntimeException.createNoSuchElementException("iterator exhausted", null);
|
||||
}
|
||||
final Entry entry = new Entry(keys[index], values[index]);
|
||||
goToNext();
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find next index.
|
||||
*/
|
||||
private void goToNext() {
|
||||
try {
|
||||
while (states[++index] != FULL) {
|
||||
// nothing to do
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Entry class for the map.
|
||||
* <p>Entry elements are built on the fly only during iteration,
|
||||
* copying values. So changes in the map are <strong>not</strong>
|
||||
* reflected on already built entries.</p>
|
||||
*/
|
||||
public static class Entry {
|
||||
|
||||
/** Key. */
|
||||
private final int key;
|
||||
|
||||
/** Value. */
|
||||
private final double value;
|
||||
|
||||
/**
|
||||
* Simple constructor.
|
||||
* @param key entry key
|
||||
* @param value entry value
|
||||
*/
|
||||
private Entry(final int key, final double value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key.
|
||||
* @return entry key
|
||||
*/
|
||||
public int key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value.
|
||||
* @return entry value
|
||||
*/
|
||||
public double value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -39,6 +39,10 @@ The <action> type attribute can be add,update,fix,remove.
|
|||
</properties>
|
||||
<body>
|
||||
<release version="2.0" date="TBD" description="TBD">
|
||||
<action dev="luc" type="add" due-to="Ismael Juma">
|
||||
Added an int/double hash map (OpenIntToDoubleHashMap) with much smaller
|
||||
memory overhead than standard java.util.Map (open addressing and no boxing).
|
||||
</action>
|
||||
<action dev="luc" type="add" issue="MATH-152" due-to="Remi Arntzen">
|
||||
Added support for multi-dimensional Fourier transform.
|
||||
</action>
|
||||
|
|
|
@ -72,7 +72,18 @@
|
|||
</p>
|
||||
</subsection>
|
||||
|
||||
<subsection name="6.3 Continued Fractions" href="continued_fractions">
|
||||
<subsection name="6.3 int/double hash map" href="int_double_hash_map">
|
||||
<p>
|
||||
The <a href="../apidocs/org/apache/commons/math/util/OpenIntToDoubleHashMap.html">
|
||||
org.apache.commons.math.util.OpenIntToDoubleHashMap</a> class provides a specialized
|
||||
hash map implementation for int/double. This implementation has a much smaller memory
|
||||
overhead than standard <code>java.util.HashMap</code> class. It uses open addressing
|
||||
and primitive arrays, which greatly reduces the number of intermediate objects and
|
||||
improve data locality.
|
||||
</p>
|
||||
</subsection>
|
||||
|
||||
<subsection name="6.4 Continued Fractions" href="continued_fractions">
|
||||
<p>
|
||||
The <a href="../apidocs/org/apache/commons/math/util/ContinuedFraction.html">
|
||||
org.apache.commons.math.util.ContinuedFraction</a> class provides a generic
|
||||
|
@ -140,7 +151,7 @@
|
|||
</p>
|
||||
</subsection>
|
||||
|
||||
<subsection name="6.4 binomial coefficients, factorials and other common math functions" href="math_utils">
|
||||
<subsection name="6.5 binomial coefficients, factorials and other common math functions" href="math_utils">
|
||||
<p>
|
||||
A collection of reusable math functions is provided in the
|
||||
<a href="../apidocs/org/apache/commons/math/util/MathUtils.html">MathUtils</a>
|
||||
|
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* 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.math.util;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Test cases for the {@link OpenIntToDoubleHashMap}.
|
||||
*/
|
||||
public class OpenIntToDoubleHashMapTest extends TestCase {
|
||||
|
||||
private Map<Integer, Double> javaMap = new HashMap<Integer, Double>();
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
javaMap.put(50, 100.0);
|
||||
javaMap.put(75, 75.0);
|
||||
javaMap.put(25, 500.0);
|
||||
javaMap.put(Integer.MAX_VALUE, Double.MAX_VALUE);
|
||||
javaMap.put(0, -1.0);
|
||||
javaMap.put(1, 0.0);
|
||||
javaMap.put(33, -0.1);
|
||||
javaMap.put(23234234, -242343.0);
|
||||
javaMap.put(23321, Double.MIN_VALUE);
|
||||
javaMap.put(-4444, 332.0);
|
||||
javaMap.put(-1, -2323.0);
|
||||
javaMap.put(Integer.MIN_VALUE, 44.0);
|
||||
|
||||
/* Add a few more to cause the table to rehash */
|
||||
javaMap.putAll(generate());
|
||||
|
||||
}
|
||||
|
||||
private Map<Integer, Double> generate() {
|
||||
Map<Integer, Double> map = new HashMap<Integer, Double>();
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < 2000; ++i)
|
||||
map.put(r.nextInt(), r.nextDouble());
|
||||
return map;
|
||||
}
|
||||
|
||||
private OpenIntToDoubleHashMap createFromJavaMap() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
|
||||
map.put(mapEntry.getKey(), mapEntry.getValue());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public void testPutAndGetWith0ExpectedSize() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap(0);
|
||||
assertPutAndGet(map);
|
||||
}
|
||||
|
||||
public void testPutAndGetWithExpectedSize() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap(500);
|
||||
assertPutAndGet(map);
|
||||
}
|
||||
|
||||
public void testPutAndGet() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
|
||||
assertPutAndGet(map);
|
||||
}
|
||||
|
||||
private void assertPutAndGet(OpenIntToDoubleHashMap map) {
|
||||
assertPutAndGet(map, 0, new HashSet<Integer>());
|
||||
}
|
||||
|
||||
private void assertPutAndGet(OpenIntToDoubleHashMap map, int mapSize,
|
||||
Set<Integer> keysInMap) {
|
||||
assertEquals(mapSize, map.size());
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
|
||||
map.put(mapEntry.getKey(), mapEntry.getValue());
|
||||
if (!keysInMap.contains(mapEntry.getKey()))
|
||||
++mapSize;
|
||||
assertEquals(mapSize, map.size());
|
||||
assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
public void testPutAbsentOnExisting() {
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
int size = javaMap.size();
|
||||
for (Map.Entry<Integer, Double> mapEntry : generateAbsent().entrySet()) {
|
||||
map.put(mapEntry.getKey(), mapEntry.getValue());
|
||||
assertEquals(++size, map.size());
|
||||
assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
public void testPutOnExisting() {
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
|
||||
map.put(mapEntry.getKey(), mapEntry.getValue());
|
||||
assertEquals(javaMap.size(), map.size());
|
||||
assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetAbsent() {
|
||||
Map<Integer, Double> generated = generateAbsent();
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
|
||||
for (Map.Entry<Integer, Double> mapEntry : generated.entrySet())
|
||||
assertEquals(0.0, map.get(mapEntry.getKey()));
|
||||
}
|
||||
|
||||
public void testGetFromEmpty() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
|
||||
assertEquals(0.0, map.get(5));
|
||||
assertEquals(0.0, map.get(0));
|
||||
assertEquals(0.0, map.get(50));
|
||||
}
|
||||
|
||||
public void testRemove() {
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
int mapSize = javaMap.size();
|
||||
assertEquals(mapSize, map.size());
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
|
||||
map.remove(mapEntry.getKey());
|
||||
assertEquals(--mapSize, map.size());
|
||||
assertEquals(0.0, map.get(mapEntry.getKey()));
|
||||
}
|
||||
|
||||
/* Ensure that put and get still work correctly after removals */
|
||||
assertPutAndGet(map);
|
||||
}
|
||||
|
||||
/* This time only remove some entries */
|
||||
public void testRemove2() {
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
int mapSize = javaMap.size();
|
||||
int count = 0;
|
||||
Set<Integer> keysInMap = new HashSet<Integer>(javaMap.keySet());
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
|
||||
keysInMap.remove(mapEntry.getKey());
|
||||
map.remove(mapEntry.getKey());
|
||||
assertEquals(--mapSize, map.size());
|
||||
assertEquals(0.0, map.get(mapEntry.getKey()));
|
||||
if (count++ > 5)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Ensure that put and get still work correctly after removals */
|
||||
assertPutAndGet(map, mapSize, keysInMap);
|
||||
}
|
||||
|
||||
public void testRemoveFromEmpty() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
|
||||
assertEquals(0.0, map.remove(50));
|
||||
}
|
||||
|
||||
public void testRemoveAbsent() {
|
||||
Map<Integer, Double> generated = generateAbsent();
|
||||
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
int mapSize = map.size();
|
||||
|
||||
for (Map.Entry<Integer, Double> mapEntry : generated.entrySet()) {
|
||||
map.remove(mapEntry.getKey());
|
||||
assertEquals(mapSize, map.size());
|
||||
assertEquals(0.0, map.get(mapEntry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map with at least 100 elements where each element is absent from javaMap.
|
||||
*/
|
||||
private Map<Integer, Double> generateAbsent() {
|
||||
Map<Integer, Double> generated = new HashMap<Integer, Double>();
|
||||
do {
|
||||
generated.putAll(generate());
|
||||
for (Integer key : javaMap.keySet())
|
||||
generated.remove(key);
|
||||
} while (generated.size() < 100);
|
||||
return generated;
|
||||
}
|
||||
|
||||
public void testCopy() {
|
||||
OpenIntToDoubleHashMap copy =
|
||||
new OpenIntToDoubleHashMap(createFromJavaMap());
|
||||
assertEquals(javaMap.size(), copy.size());
|
||||
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet())
|
||||
assertEquals(mapEntry.getValue(), copy.get(mapEntry.getKey()));
|
||||
}
|
||||
|
||||
public void testContainsKey() {
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
|
||||
assertTrue(map.containsKey(mapEntry.getKey()));
|
||||
}
|
||||
for (Map.Entry<Integer, Double> mapEntry : generateAbsent().entrySet()) {
|
||||
assertFalse(map.containsKey(mapEntry.getKey()));
|
||||
}
|
||||
for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
|
||||
int key = mapEntry.getKey();
|
||||
assertTrue(map.containsKey(key));
|
||||
map.remove(key);
|
||||
assertFalse(map.containsKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
public void testIterator() {
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
OpenIntToDoubleHashMap.Iterator iterator = map.iterator();
|
||||
for (int i = 0; i < map.size(); ++i) {
|
||||
assertTrue(iterator.hasNext());
|
||||
OpenIntToDoubleHashMap.Entry entry = iterator.next();
|
||||
int key = entry.key();
|
||||
assertTrue(map.containsKey(key));
|
||||
assertEquals(javaMap.get(key), map.get(key), 0);
|
||||
assertTrue(javaMap.containsKey(key));
|
||||
}
|
||||
assertFalse(iterator.hasNext());
|
||||
try {
|
||||
iterator.next();
|
||||
} catch (NoSuchElementException nsee) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testConcurrentModification() {
|
||||
OpenIntToDoubleHashMap map = createFromJavaMap();
|
||||
OpenIntToDoubleHashMap.Iterator iterator = map.iterator();
|
||||
map.put(3, 3);
|
||||
try {
|
||||
iterator.next();
|
||||
} catch (ConcurrentModificationException cme) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test for a bug in findInsertionIndex where the hashing in the second probing
|
||||
* loop was inconsistent with the first causing duplicate keys after the right sequence
|
||||
* of puts and removes.
|
||||
*/
|
||||
public void testPutKeysWithCollisions() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
|
||||
int key1 = -1996012590;
|
||||
double value1 = 1.0;
|
||||
map.put(key1, value1);
|
||||
int key2 = 835099822;
|
||||
map.put(key2, value1);
|
||||
int key3 = 1008859686;
|
||||
map.put(key3, value1);
|
||||
assertEquals(value1, map.get(key3));
|
||||
assertEquals(3, map.size());
|
||||
|
||||
map.remove(key2);
|
||||
double value2 = 2.0;
|
||||
map.put(key3, value2);
|
||||
assertEquals(value2, map.get(key3));
|
||||
assertEquals(2, map.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to testPutKeysWithCollisions() but exercises the codepaths in a slightly
|
||||
* different manner.
|
||||
*/
|
||||
public void testPutKeysWithCollision2() {
|
||||
OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
|
||||
int key1 = 837989881;
|
||||
double value1 = 1.0;
|
||||
map.put(key1, value1);
|
||||
int key2 = 476463321;
|
||||
map.put(key2, value1);
|
||||
assertEquals(2, map.size());
|
||||
assertEquals(value1, map.get(key2));
|
||||
|
||||
map.remove(key1);
|
||||
double value2 = 2.0;
|
||||
map.put(key2, value2);
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(value2, map.get(key2));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue