Added MersenneTwisterFast (replaces java.util.Random), added missing licenses. Added field annonation exclusion serilaizer. Code polish

This commit is contained in:
nathan 2014-08-25 01:56:20 +02:00
parent bbea13e42d
commit 142ab8a383
7 changed files with 3466 additions and 0 deletions

94
Dorkbox-Util/LICENSE.TXT Normal file
View File

@ -0,0 +1,94 @@
Legal:
- Copyright (c) 2010 and beyond, dorkbox llc. All rights reserved.
This software is a commercial product. Use, redistribution, and
modification without a license is NOT permitted. Contact
license@dorkbox.com for a license.
Neither the name of dorkbox, dorkbox llc, or dorkbox.com or the names of
its contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE OR RESALE AS
ON-LINE CONTROL EQUIPMENT IN HAZARDOUS ENVIRONMENTS REQUIRING FAIL-SAFE
PERFORMANCE, SUCH AS IN THE OPERATION OF NUCLEAR FACILITIES, AIRCRAFT
NAVIGATION OR COMMUNICATION SYSTEMS, AIR TRAFFIC CONTROL, DIRECT LIFE
SUPPORT MACHINES, OR WEAPONS SYSTEMS, IN WHICH THE FAILURE OF THE SOFTWARE
COULD LEAD DIRECTLY TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL OR
ENVIRONMENTAL DAMAGE ("HIGH RISK ACTIVITIES").
THIS AGREEMENT IS GOVERNED THE INTELLECTUAL PROPERTY LAWS OF THE UNITED
STATES OF AMERICA. NO PARTY TO THIS AGREEMENT WILL BRING A LEGAL ACTION
UNDER THIS AGREEMENT MORE THAN ONE YEAR AFTER THE CAUSE OF ACTION AROSE.
EACH PARTY WAIVES ITS RIGHTS TO A JURY TRIAL IN ANY RESULTING LITIGATION.
- This software product uses encryption algorithms that are controlled by
the United States Export Administration Regulations (EAR).
https://bxa.ntis.gov/
Details of the U.S. Commercial Encryption Export Controls can be found at
the Bureau of Industry and Security (BIS) web site.
http://www.bis.doc.gov/
PROHIBITED END USERS
ALL products are prohibited for export/reexport to the following:
- Any company or national of Cuba, Iran, North Korea, Sudan, and Syria.
Licenses to these countries and parties are presumed denied.
- Re-export to these countries is prohibited; if you "know or have reason
to know" that an illegal reshipment will take place, you may not ship to
such a user.
- Entities listed on any U.S. Government Denied Party/Person List. See BIS's
The Denied Persons List, the Office of Foreign Assets Control's Economic
and Trade sanctions list, (OFAC), and the Office of Defense Trade
Controls (DTC).
- Any customer you know or have reason to know, who is involved in the
design, development, manufacture or production of nuclear technology, or
nuclear, biological or chemical "weapons of mass destruction."
All products are subject to the U.S. Export Laws, and diversion contrary
to U.S. law is prohibited.
- BouncyCastle - MIT X11 License
http://www.bouncycastle.org
Copyright (c) 2000 - 2009 The Legion Of The Bouncy Castle
- MathUtils, IntArray, IntMap - Apache 2.0 license
http://github.com/libgdx/libgdx/
Copyright (c) 2013
Mario Zechner <badlogicgames@gmail.com>
Nathan Sweet <nathan.sweet@gmail.com>
- MersenneTwisterFast, v20 - BSD license
http://www.cs.gmu.edu/~sean/research/mersenne/MersenneTwisterFast.java
Copyright (c) 2003 by Sean Luke.
Portions copyright (c) 1993 by Michael Lecuyer.

View File

@ -0,0 +1,89 @@
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed 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.
*/
// pruned/limited version from libGDX
package dorkbox.util;
public class MathUtils {
// ---
private static ThreadLocal<MersenneTwisterFast> random = new ThreadLocal<MersenneTwisterFast>();
/** Returns a random integer */
static public final int randomInt () {
return random.get().nextInt();
}
/** Returns a random number between 0 (inclusive) and the specified value (inclusive). */
static public final int randomInt (int range) {
return random.get().nextInt(range + 1);
}
/** Returns a random number between start (inclusive) and end (inclusive). */
static public final int randomInt (int start, int end) {
return start + random.get().nextInt(end - start + 1);
}
/** Returns a random boolean value. */
static public final boolean randomBoolean () {
return random.get().nextBoolean();
}
/** Returns random number between 0.0 (inclusive) and 1.0 (exclusive). */
static public final float randomFloat () {
return random.get().nextFloat();
}
/** Returns a random number between 0 (inclusive) and the specified value (exclusive). */
static public final float randomFloat (float range) {
return random.get().nextFloat() * range;
}
/** Returns a random number between start (inclusive) and end (exclusive). */
static public final float randomFloat (float start, float end) {
return start + random.get().nextFloat() * (end - start);
}
// ---
/**
* Returns the next power of two. Returns the specified value if the value
* is already a power of two.
*/
public static int nextPowerOfTwo(int value) {
if (value == 0) {
return 1;
}
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}
public static boolean isPowerOfTwo(int value) {
return value != 0 && (value & value - 1) == 0;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,384 @@
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed 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.
*/
// from libGDX
package dorkbox.util.primativeCollections;
import java.util.Arrays;
import dorkbox.util.MathUtils;
/** A resizable, ordered or unordered int array. Avoids the boxing that occurs with ArrayList<Integer>. If unordered, this class
* avoids a memory copy when removing elements (the last element is moved to the removed element's position).
* @author Nathan Sweet */
public class IntArray {
public int[] items;
public int size;
public boolean ordered;
/** Creates an ordered array with a capacity of 16. */
public IntArray () {
this(true, 16);
}
/** Creates an ordered array with the specified capacity. */
public IntArray (int capacity) {
this(true, capacity);
}
/** @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
* memory copy.
* @param capacity Any elements added beyond this will cause the backing array to be grown. */
public IntArray (boolean ordered, int capacity) {
this.ordered = ordered;
this.items = new int[capacity];
}
/** Creates a new array containing the elements in the specific array. The new array will be ordered if the specific array is
* ordered. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array to be
* grown. */
public IntArray (IntArray array) {
this.ordered = array.ordered;
this.size = array.size;
this.items = new int[this.size];
System.arraycopy(array.items, 0, this.items, 0, this.size);
}
/** Creates a new ordered array containing the elements in the specified array. The capacity is set to the number of elements,
* so any subsequent elements added will cause the backing array to be grown. */
public IntArray (int[] array) {
this(true, array, 0, array.length);
}
/** Creates a new array containing the elements in the specified array. The capacity is set to the number of elements, so any
* subsequent elements added will cause the backing array to be grown.
* @param ordered If false, methods that remove elements may change the order of other elements in the array, which avoids a
* memory copy. */
public IntArray (boolean ordered, int[] array, int startIndex, int count) {
this(ordered, array.length);
this.size = count;
System.arraycopy(array, startIndex, this.items, 0, count);
}
public void add (int value) {
int[] items = this.items;
if (this.size == items.length) {
items = resize(Math.max(8, (int)(this.size * 1.75f)));
}
items[this.size++] = value;
}
public void addAll (IntArray array) {
addAll(array, 0, array.size);
}
public void addAll (IntArray array, int offset, int length) {
if (offset + length > array.size) {
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
}
addAll(array.items, offset, length);
}
public void addAll (int[] array) {
addAll(array, 0, array.length);
}
public void addAll (int[] array, int offset, int length) {
int[] items = this.items;
int sizeNeeded = this.size + length;
if (sizeNeeded >= items.length) {
items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
}
System.arraycopy(array, offset, items, this.size, length);
this.size += length;
}
public int get (int index) {
if (index >= this.size) {
throw new IndexOutOfBoundsException(String.valueOf(index));
}
return this.items[index];
}
public void set (int index, int value) {
if (index >= this.size) {
throw new IndexOutOfBoundsException(String.valueOf(index));
}
this.items[index] = value;
}
public void insert (int index, int value) {
if (index > this.size) {
throw new IndexOutOfBoundsException(String.valueOf(index));
}
int[] items = this.items;
if (this.size == items.length) {
items = resize(Math.max(8, (int)(this.size * 1.75f)));
}
if (this.ordered) {
System.arraycopy(items, index, items, index + 1, this.size - index);
} else {
items[this.size] = items[index];
}
this.size++;
items[index] = value;
}
public void swap (int first, int second) {
if (first >= this.size) {
throw new IndexOutOfBoundsException(String.valueOf(first));
}
if (second >= this.size) {
throw new IndexOutOfBoundsException(String.valueOf(second));
}
int[] items = this.items;
int firstValue = items[first];
items[first] = items[second];
items[second] = firstValue;
}
public boolean contains (int value) {
int i = this.size - 1;
int[] items = this.items;
while (i >= 0) {
if (items[i--] == value) {
return true;
}
}
return false;
}
public int indexOf (int value) {
int[] items = this.items;
for (int i = 0, n = this.size; i < n; i++) {
if (items[i] == value) {
return i;
}
}
return -1;
}
public int lastIndexOf (int value) {
int[] items = this.items;
for (int i = this.size - 1; i >= 0; i--) {
if (items[i] == value) {
return i;
}
}
return -1;
}
public boolean removeValue (int value) {
int[] items = this.items;
for (int i = 0, n = this.size; i < n; i++) {
if (items[i] == value) {
removeIndex(i);
return true;
}
}
return false;
}
/** Removes and returns the item at the specified index. */
public int removeIndex (int index) {
if (index >= this.size) {
throw new IndexOutOfBoundsException(String.valueOf(index));
}
int[] items = this.items;
int value = items[index];
this.size--;
if (this.ordered) {
System.arraycopy(items, index + 1, items, index, this.size - index);
} else {
items[index] = items[this.size];
}
return value;
}
/** Removes from this array all of elements contained in the specified array.
* @return true if this array was modified. */
public boolean removeAll (IntArray array) {
int size = this.size;
int startSize = size;
int[] items = this.items;
for (int i = 0, n = array.size; i < n; i++) {
int item = array.get(i);
for (int ii = 0; ii < size; ii++) {
if (item == items[ii]) {
removeIndex(ii);
size--;
break;
}
}
}
return size != startSize;
}
/** Removes and returns the last item. */
public int pop () {
return this.items[--this.size];
}
/** Returns the last item. */
public int peek () {
return this.items[this.size - 1];
}
/** Returns the first item. */
public int first () {
if (this.size == 0) {
throw new IllegalStateException("Array is empty.");
}
return this.items[0];
}
public void clear () {
this.size = 0;
}
/** Reduces the size of the backing array to the size of the actual items. This is useful to release memory when many items have
* been removed, or if it is known that more items will not be added. */
public void shrink () {
resize(this.size);
}
/** Increases the size of the backing array to acommodate the specified number of additional items. Useful before adding many
* items to avoid multiple backing array resizes.
* @return {@link #items} */
public int[] ensureCapacity (int additionalCapacity) {
int sizeNeeded = this.size + additionalCapacity;
if (sizeNeeded >= this.items.length) {
resize(Math.max(8, sizeNeeded));
}
return this.items;
}
protected int[] resize (int newSize) {
int[] newItems = new int[newSize];
int[] items = this.items;
System.arraycopy(items, 0, newItems, 0, Math.min(this.size, newItems.length));
this.items = newItems;
return newItems;
}
public void sort () {
Arrays.sort(this.items, 0, this.size);
}
public void reverse () {
for (int i = 0, lastIndex = this.size - 1, n = this.size / 2; i < n; i++) {
int ii = lastIndex - i;
int temp = this.items[i];
this.items[i] = this.items[ii];
this.items[ii] = temp;
}
}
public void shuffle () {
for (int i = this.size - 1; i >= 0; i--) {
int ii = MathUtils.randomInt(i);
int temp = this.items[i];
this.items[i] = this.items[ii];
this.items[ii] = temp;
}
}
/** Reduces the size of the array to the specified size. If the array is already smaller than the specified size, no action is
* taken. */
public void truncate (int newSize) {
if (this.size > newSize) {
this.size = newSize;
}
}
/** Returns a random item from the array, or zero if the array is empty. */
public int random () {
if (this.size == 0) {
return 0;
}
return this.items[MathUtils.randomInt(0, this.size - 1)];
}
public int[] toArray () {
int[] array = new int[this.size];
System.arraycopy(this.items, 0, array, 0, this.size);
return array;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(this.items);
result = prime * result + (this.ordered ? 1231 : 1237);
result = prime * result + this.size;
return result;
}
@Override
public boolean equals (Object object) {
if (object == this) {
return true;
}
if (!(object instanceof IntArray)) {
return false;
}
IntArray array = (IntArray)object;
int n = this.size;
if (n != array.size) {
return false;
}
for (int i = 0; i < n; i++) {
if (this.items[i] != array.items[i]) {
return false;
}
}
return true;
}
@Override
public String toString () {
if (this.size == 0) {
return "[]";
}
int[] items = this.items;
StringBuilder buffer = new StringBuilder(32);
buffer.append('[');
buffer.append(items[0]);
for (int i = 1; i < this.size; i++) {
buffer.append(", ");
buffer.append(items[i]);
}
buffer.append(']');
return buffer.toString();
}
public String toString (String separator) {
if (this.size == 0) {
return "";
}
int[] items = this.items;
StringBuilder buffer = new StringBuilder(32);
buffer.append(items[0]);
for (int i = 1; i < this.size; i++) {
buffer.append(separator);
buffer.append(items[i]);
}
return buffer.toString();
}
}

View File

@ -0,0 +1,870 @@
/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed 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.
*/
// slightly tweaked from libGDX
package dorkbox.util.primativeCollections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import dorkbox.util.MathUtils;
/** An unordered map that uses int keys. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash
* for problematic keys. Null values are allowed. No allocation is done except when growing the table size. <br>
* <br>
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
* next higher POT size.
* @author Nathan Sweet */
@SuppressWarnings({"rawtypes", "unchecked"})
public class IntMap<V> {
@SuppressWarnings("unused")
private static final int PRIME1 = 0xbe1f14b1;
private static final int PRIME2 = 0xb4b82e39;
private static final int PRIME3 = 0xced1c241;
private static final int EMPTY = 0;
public int size;
int[] keyTable;
V[] valueTable;
int capacity, stashSize;
V zeroValue;
boolean hasZeroValue;
private float loadFactor;
private int hashShift, mask, threshold;
private int stashCapacity;
private int pushIterations;
private Entries entries1, entries2;
private Values values1, values2;
private Keys keys1, keys2;
/** Creates a new map with an initial capacity of 32 and a load factor of 0.8. This map will hold 25 items before growing the
* backing table. */
public IntMap () {
this(32, 0.8f);
}
/** Creates a new map with a load factor of 0.8. This map will hold initialCapacity * 0.8 items before growing the backing
* table. */
public IntMap (int initialCapacity) {
this(initialCapacity, 0.8f);
}
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity * loadFactor items
* before growing the backing table. */
public IntMap (int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
}
if (this.capacity > 1 << 30) {
throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
}
this.capacity = MathUtils.nextPowerOfTwo(initialCapacity);
if (loadFactor <= 0) {
throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
}
this.loadFactor = loadFactor;
this.threshold = (int)(this.capacity * loadFactor);
this.mask = this.capacity - 1;
this.hashShift = 31 - Integer.numberOfTrailingZeros(this.capacity);
this.stashCapacity = Math.max(3, (int)Math.ceil(Math.log(this.capacity)) * 2);
this.pushIterations = Math.max(Math.min(this.capacity, 8), (int)Math.sqrt(this.capacity) / 8);
this.keyTable = new int[this.capacity + this.stashCapacity];
this.valueTable = (V[])new Object[this.keyTable.length];
}
public V put (int key, V value) {
if (key == 0) {
V oldValue = this.zeroValue;
this.zeroValue = value;
if (!this.hasZeroValue) {
this.hasZeroValue = true;
this.size++;
}
return oldValue;
}
int[] keyTable = this.keyTable;
// Check for existing keys.
int index1 = key & this.mask;
int key1 = keyTable[index1];
if (key1 == key) {
V oldValue = this.valueTable[index1];
this.valueTable[index1] = value;
return oldValue;
}
int index2 = hash2(key);
int key2 = keyTable[index2];
if (key2 == key) {
V oldValue = this.valueTable[index2];
this.valueTable[index2] = value;
return oldValue;
}
int index3 = hash3(key);
int key3 = keyTable[index3];
if (key3 == key) {
V oldValue = this.valueTable[index3];
this.valueTable[index3] = value;
return oldValue;
}
// Update key in the stash.
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (keyTable[i] == key) {
V oldValue = this.valueTable[i];
this.valueTable[i] = value;
return oldValue;
}
}
// Check for empty buckets.
if (key1 == EMPTY) {
keyTable[index1] = key;
this.valueTable[index1] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return null;
}
if (key2 == EMPTY) {
keyTable[index2] = key;
this.valueTable[index2] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return null;
}
if (key3 == EMPTY) {
keyTable[index3] = key;
this.valueTable[index3] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return null;
}
push(key, value, index1, key1, index2, key2, index3, key3);
return null;
}
public void putAll (IntMap<V> map) {
for (Entry<V> entry : map.entries()) {
put(entry.key, entry.value);
}
}
/** Skips checks for existing keys. */
private void putResize (int key, V value) {
if (key == 0) {
this.zeroValue = value;
this.hasZeroValue = true;
return;
}
// Check for empty buckets.
int index1 = key & this.mask;
int key1 = this.keyTable[index1];
if (key1 == EMPTY) {
this.keyTable[index1] = key;
this.valueTable[index1] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
int index2 = hash2(key);
int key2 = this.keyTable[index2];
if (key2 == EMPTY) {
this.keyTable[index2] = key;
this.valueTable[index2] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
int index3 = hash3(key);
int key3 = this.keyTable[index3];
if (key3 == EMPTY) {
this.keyTable[index3] = key;
this.valueTable[index3] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
push(key, value, index1, key1, index2, key2, index3, key3);
}
private void push (int insertKey, V insertValue, int index1, int key1, int index2, int key2, int index3, int key3) {
int[] keyTable = this.keyTable;
V[] valueTable = this.valueTable;
int mask = this.mask;
// Push keys until an empty bucket is found.
int evictedKey;
V evictedValue;
int i = 0, pushIterations = this.pushIterations;
do {
// Replace the key and value for one of the hashes.
switch (MathUtils.randomInt(2)) {
case 0:
evictedKey = key1;
evictedValue = valueTable[index1];
keyTable[index1] = insertKey;
valueTable[index1] = insertValue;
break;
case 1:
evictedKey = key2;
evictedValue = valueTable[index2];
keyTable[index2] = insertKey;
valueTable[index2] = insertValue;
break;
default:
evictedKey = key3;
evictedValue = valueTable[index3];
keyTable[index3] = insertKey;
valueTable[index3] = insertValue;
break;
}
// If the evicted key hashes to an empty bucket, put it there and stop.
index1 = evictedKey & mask;
key1 = keyTable[index1];
if (key1 == EMPTY) {
keyTable[index1] = evictedKey;
valueTable[index1] = evictedValue;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
index2 = hash2(evictedKey);
key2 = keyTable[index2];
if (key2 == EMPTY) {
keyTable[index2] = evictedKey;
valueTable[index2] = evictedValue;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
index3 = hash3(evictedKey);
key3 = keyTable[index3];
if (key3 == EMPTY) {
keyTable[index3] = evictedKey;
valueTable[index3] = evictedValue;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
if (++i == pushIterations) {
break;
}
insertKey = evictedKey;
insertValue = evictedValue;
} while (true);
putStash(evictedKey, evictedValue);
}
private void putStash (int key, V value) {
if (this.stashSize == this.stashCapacity) {
// Too many pushes occurred and the stash is full, increase the table size.
resize(this.capacity << 1);
put(key, value);
return;
}
// Store key in the stash.
int index = this.capacity + this.stashSize;
this.keyTable[index] = key;
this.valueTable[index] = value;
this.stashSize++;
this.size++;
}
public V get (int key) {
if (key == 0) {
if (!this.hasZeroValue) {
return null;
}
return this.zeroValue;
}
int index = key & this.mask;
if (this.keyTable[index] != key) {
index = hash2(key);
if (this.keyTable[index] != key) {
index = hash3(key);
if (this.keyTable[index] != key) {
return getStash(key, null);
}
}
}
return this.valueTable[index];
}
public V get (int key, V defaultValue) {
if (key == 0) {
if (!this.hasZeroValue) {
return defaultValue;
}
return this.zeroValue;
}
int index = key & this.mask;
if (this.keyTable[index] != key) {
index = hash2(key);
if (this.keyTable[index] != key) {
index = hash3(key);
if (this.keyTable[index] != key) {
return getStash(key, defaultValue);
}
}
}
return this.valueTable[index];
}
private V getStash (int key, V defaultValue) {
int[] keyTable = this.keyTable;
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (keyTable[i] == key) {
return this.valueTable[i];
}
}
return defaultValue;
}
public V remove (int key) {
if (key == 0) {
if (!this.hasZeroValue) {
return null;
}
V oldValue = this.zeroValue;
this.zeroValue = null;
this.hasZeroValue = false;
this.size--;
return oldValue;
}
int index = key & this.mask;
if (this.keyTable[index] == key) {
this.keyTable[index] = EMPTY;
V oldValue = this.valueTable[index];
this.valueTable[index] = null;
this.size--;
return oldValue;
}
index = hash2(key);
if (this.keyTable[index] == key) {
this.keyTable[index] = EMPTY;
V oldValue = this.valueTable[index];
this.valueTable[index] = null;
this.size--;
return oldValue;
}
index = hash3(key);
if (this.keyTable[index] == key) {
this.keyTable[index] = EMPTY;
V oldValue = this.valueTable[index];
this.valueTable[index] = null;
this.size--;
return oldValue;
}
return removeStash(key);
}
V removeStash (int key) {
int[] keyTable = this.keyTable;
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (keyTable[i] == key) {
V oldValue = this.valueTable[i];
removeStashIndex(i);
this.size--;
return oldValue;
}
}
return null;
}
void removeStashIndex (int index) {
// If the removed location was not last, move the last tuple to the removed location.
this.stashSize--;
int lastIndex = this.capacity + this.stashSize;
if (index < lastIndex) {
this.keyTable[index] = this.keyTable[lastIndex];
this.valueTable[index] = this.valueTable[lastIndex];
this.valueTable[lastIndex] = null;
} else {
this.valueTable[index] = null;
}
}
public void clear () {
int[] keyTable = this.keyTable;
V[] valueTable = this.valueTable;
for (int i = this.capacity + this.stashSize; i-- > 0;) {
keyTable[i] = EMPTY;
valueTable[i] = null;
}
this.size = 0;
this.stashSize = 0;
this.zeroValue = null;
this.hasZeroValue = false;
}
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
* an expensive operation.
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
* {@link #equals(Object)}. */
public boolean containsValue (Object value, boolean identity) {
V[] valueTable = this.valueTable;
if (value == null) {
if (this.hasZeroValue && this.zeroValue == null) {
return true;
}
int[] keyTable = this.keyTable;
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (keyTable[i] != EMPTY && valueTable[i] == null) {
return true;
}
}
} else if (identity) {
if (value == this.zeroValue) {
return true;
}
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (valueTable[i] == value) {
return true;
}
}
} else {
if (this.hasZeroValue && value.equals(this.zeroValue)) {
return true;
}
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (value.equals(valueTable[i])) {
return true;
}
}
}
return false;
}
public boolean containsKey (int key) {
if (key == 0) {
return this.hasZeroValue;
}
int index = key & this.mask;
if (this.keyTable[index] != key) {
index = hash2(key);
if (this.keyTable[index] != key) {
index = hash3(key);
if (this.keyTable[index] != key) {
return containsKeyStash(key);
}
}
}
return true;
}
private boolean containsKeyStash (int key) {
int[] keyTable = this.keyTable;
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (keyTable[i] == key) {
return true;
}
}
return false;
}
/** Returns the key for the specified value, or <tt>notFound</tt> if it is not in the map. Note this traverses the entire map
* and compares every value, which may be an expensive operation.
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
* {@link #equals(Object)}. */
public int findKey (Object value, boolean identity, int notFound) {
V[] valueTable = this.valueTable;
if (value == null) {
if (this.hasZeroValue && this.zeroValue == null) {
return 0;
}
int[] keyTable = this.keyTable;
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (keyTable[i] != EMPTY && valueTable[i] == null) {
return keyTable[i];
}
}
} else if (identity) {
if (value == this.zeroValue) {
return 0;
}
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (valueTable[i] == value) {
return this.keyTable[i];
}
}
} else {
if (this.hasZeroValue && value.equals(this.zeroValue)) {
return 0;
}
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (value.equals(valueTable[i])) {
return this.keyTable[i];
}
}
}
return notFound;
}
/** Increases the size of the backing array to acommodate the specified number of additional items. Useful before adding many
* items to avoid multiple backing array resizes. */
public void ensureCapacity (int additionalCapacity) {
int sizeNeeded = this.size + additionalCapacity;
if (sizeNeeded >= this.threshold) {
resize(MathUtils.nextPowerOfTwo((int)(sizeNeeded / this.loadFactor)));
}
}
private void resize (int newSize) {
int oldEndIndex = this.capacity + this.stashSize;
this.capacity = newSize;
this.threshold = (int)(newSize * this.loadFactor);
this.mask = newSize - 1;
this.hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
this.stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
this.pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
int[] oldKeyTable = this.keyTable;
V[] oldValueTable = this.valueTable;
this.keyTable = new int[newSize + this.stashCapacity];
this.valueTable = (V[])new Object[newSize + this.stashCapacity];
this.size = this.hasZeroValue ? 1 : 0;
this.stashSize = 0;
for (int i = 0; i < oldEndIndex; i++) {
int key = oldKeyTable[i];
if (key != EMPTY) {
putResize(key, oldValueTable[i]);
}
}
}
private int hash2 (int h) {
h *= PRIME2;
return (h ^ h >>> this.hashShift) & this.mask;
}
private int hash3 (int h) {
h *= PRIME3;
return (h ^ h >>> this.hashShift) & this.mask;
}
@Override
public String toString () {
if (this.size == 0) {
return "[]";
}
StringBuilder buffer = new StringBuilder(32);
buffer.append('[');
int[] keyTable = this.keyTable;
V[] valueTable = this.valueTable;
int i = keyTable.length;
if (this.hasZeroValue) {
buffer.append("0=");
buffer.append(this.zeroValue);
} else {
while (i-- > 0) {
int key = keyTable[i];
if (key == EMPTY) {
continue;
}
buffer.append(key);
buffer.append('=');
buffer.append(valueTable[i]);
break;
}
}
while (i-- > 0) {
int key = keyTable[i];
if (key == EMPTY) {
continue;
}
buffer.append(", ");
buffer.append(key);
buffer.append('=');
buffer.append(valueTable[i]);
}
buffer.append(']');
return buffer.toString();
}
/** Returns an iterator for the entries in the map. Remove is supported. Note that the same iterator instance is returned each
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
public Entries<V> entries () {
if (this.entries1 == null) {
this.entries1 = new Entries(this);
this.entries2 = new Entries(this);
}
if (!this.entries1.valid) {
this.entries1.reset();
this.entries1.valid = true;
this.entries2.valid = false;
return this.entries1;
}
this.entries2.reset();
this.entries2.valid = true;
this.entries1.valid = false;
return this.entries2;
}
/** Returns an iterator for the values in the map. Remove is supported. Note that the same iterator instance is returned each
* time this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
public Values<V> values () {
if (this.values1 == null) {
this.values1 = new Values(this);
this.values2 = new Values(this);
}
if (!this.values1.valid) {
this.values1.reset();
this.values1.valid = true;
this.values2.valid = false;
return this.values1;
}
this.values2.reset();
this.values2.valid = true;
this.values1.valid = false;
return this.values2;
}
/** Returns an iterator for the keys in the map. Remove is supported. Note that the same iterator instance is returned each time
* this method is called. Use the {@link Entries} constructor for nested or multithreaded iteration. */
public Keys keys () {
if (this.keys1 == null) {
this.keys1 = new Keys(this);
this.keys2 = new Keys(this);
}
if (!this.keys1.valid) {
this.keys1.reset();
this.keys1.valid = true;
this.keys2.valid = false;
return this.keys1;
}
this.keys2.reset();
this.keys2.valid = true;
this.keys1.valid = false;
return this.keys2;
}
static public class Entry<V> {
public int key;
public V value;
@Override
public String toString () {
return this.key + "=" + this.value;
}
}
static private class MapIterator<V> {
static final int INDEX_ILLEGAL = -2;
static final int INDEX_ZERO = -1;
public boolean hasNext;
final IntMap<V> map;
int nextIndex, currentIndex;
boolean valid = true;
public MapIterator (IntMap<V> map) {
this.map = map;
reset();
}
public void reset () {
this.currentIndex = INDEX_ILLEGAL;
this.nextIndex = INDEX_ZERO;
if (this.map.hasZeroValue) {
this.hasNext = true;
} else {
findNextIndex();
}
}
void findNextIndex () {
this.hasNext = false;
int[] keyTable = this.map.keyTable;
for (int n = this.map.capacity + this.map.stashSize; ++this.nextIndex < n;) {
if (keyTable[this.nextIndex] != EMPTY) {
this.hasNext = true;
break;
}
}
}
public void remove () {
if (this.currentIndex == INDEX_ZERO && this.map.hasZeroValue) {
this.map.zeroValue = null;
this.map.hasZeroValue = false;
} else if (this.currentIndex < 0) {
throw new IllegalStateException("next must be called before remove.");
} else if (this.currentIndex >= this.map.capacity) {
this.map.removeStashIndex(this.currentIndex);
} else {
this.map.keyTable[this.currentIndex] = EMPTY;
this.map.valueTable[this.currentIndex] = null;
}
this.currentIndex = INDEX_ILLEGAL;
this.map.size--;
}
}
static public class Entries<V> extends MapIterator<V> implements Iterable<Entry<V>>, Iterator<Entry<V>> {
private Entry<V> entry = new Entry();
public Entries (IntMap map) {
super(map);
}
/** Note the same entry instance is returned each time this method is called. */
@Override
public Entry<V> next () {
if (!this.hasNext) {
throw new NoSuchElementException();
}
if (!this.valid) {
throw new RuntimeException("#iterator() cannot be used nested.");
}
int[] keyTable = this.map.keyTable;
if (this.nextIndex == INDEX_ZERO) {
this.entry.key = 0;
this.entry.value = this.map.zeroValue;
} else {
this.entry.key = keyTable[this.nextIndex];
this.entry.value = this.map.valueTable[this.nextIndex];
}
this.currentIndex = this.nextIndex;
findNextIndex();
return this.entry;
}
@Override
public boolean hasNext () {
return this.hasNext;
}
@Override
public Iterator<Entry<V>> iterator () {
return this;
}
}
static public class Values<V> extends MapIterator<V> implements Iterable<V>, Iterator<V> {
public Values (IntMap<V> map) {
super(map);
}
@Override
public boolean hasNext () {
return this.hasNext;
}
@Override
public V next () {
if (!this.hasNext) {
throw new NoSuchElementException();
}
if (!this.valid) {
throw new RuntimeException("#iterator() cannot be used nested.");
}
V value;
if (this.nextIndex == INDEX_ZERO) {
value = this.map.zeroValue;
} else {
value = this.map.valueTable[this.nextIndex];
}
this.currentIndex = this.nextIndex;
findNextIndex();
return value;
}
@Override
public Iterator<V> iterator () {
return this;
}
/** Returns a new array containing the remaining values. */
// public Array<V> toArray () {
// Array array = new Array(true, map.size);
// while (hasNext) {
// array.add(next());
// }
// return array;
// }
}
static public class Keys extends MapIterator {
public Keys (IntMap map) {
super(map);
}
public int next () {
if (!this.hasNext) {
throw new NoSuchElementException();
}
if (!this.valid) {
throw new RuntimeException("#iterator() cannot be used nested.");
}
int key = this.nextIndex == INDEX_ZERO ? 0 : this.map.keyTable[this.nextIndex];
this.currentIndex = this.nextIndex;
findNextIndex();
return key;
}
/** Returns a new array containing the remaining keys. */
public IntArray toArray () {
IntArray array = new IntArray(true, this.map.size);
while (this.hasNext) {
array.add(next());
}
return array;
}
}
}

View File

@ -0,0 +1,557 @@
package dorkbox.util.primativeCollections;
import com.esotericsoftware.kryo.util.ObjectMap;
import dorkbox.util.MathUtils;
/** An unordered map where the values are ints. This implementation is a cuckoo hash map using 3 hashes, random walking, and a
* small stash for problematic keys. Null keys are not allowed. No allocation is done except when growing the table size. <br>
* <br>
* This map performs very fast get, containsKey, and remove (typically O(1), worst case O(log(n))). Put may be a bit slower,
* depending on hash collisions. Load factors greater than 0.91 greatly increase the chances the map will have to rehash to the
* next higher POT size.
* @author Nathan Sweet */
public class ObjectIntMap<K> {
@SuppressWarnings("unused")
private static final int PRIME1 = 0xbe1f14b1;
private static final int PRIME2 = 0xb4b82e39;
private static final int PRIME3 = 0xced1c241;
public int size;
K[] keyTable;
int[] valueTable;
int capacity, stashSize;
private float loadFactor;
private int hashShift, mask, threshold;
private int stashCapacity;
private int pushIterations;
/** Creates a new map with an initial capacity of 32 and a load factor of 0.8. This map will hold 25 items before growing the
* backing table. */
public ObjectIntMap () {
this(32, 0.8f);
}
/** Creates a new map with a load factor of 0.8. This map will hold initialCapacity * 0.8 items before growing the backing
* table. */
public ObjectIntMap (int initialCapacity) {
this(initialCapacity, 0.8f);
}
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity * loadFactor items
* before growing the backing table. */
@SuppressWarnings("unchecked")
public ObjectIntMap (int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
}
if (initialCapacity > 1 << 30) {
throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
}
this.capacity = ObjectMap.nextPowerOfTwo(initialCapacity);
if (loadFactor <= 0) {
throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
}
this.loadFactor = loadFactor;
this.threshold = (int)(this.capacity * loadFactor);
this.mask = this.capacity - 1;
this.hashShift = 31 - Integer.numberOfTrailingZeros(this.capacity);
this.stashCapacity = Math.max(3, (int)Math.ceil(Math.log(this.capacity)) * 2);
this.pushIterations = Math.max(Math.min(this.capacity, 8), (int)Math.sqrt(this.capacity) / 8);
this.keyTable = (K[])new Object[this.capacity + this.stashCapacity];
this.valueTable = new int[this.keyTable.length];
}
/** Creates a new map identical to the specified map. */
public ObjectIntMap (ObjectIntMap<? extends K> map) {
this(map.capacity, map.loadFactor);
this.stashSize = map.stashSize;
System.arraycopy(map.keyTable, 0, this.keyTable, 0, map.keyTable.length);
System.arraycopy(map.valueTable, 0, this.valueTable, 0, map.valueTable.length);
this.size = map.size;
}
public void put (K key, int value) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null.");
}
K[] keyTable = this.keyTable;
// Check for existing keys.
int hashCode = key.hashCode();
int index1 = hashCode & this.mask;
K key1 = keyTable[index1];
if (key.equals(key1)) {
this.valueTable[index1] = value;
return;
}
int index2 = hash2(hashCode);
K key2 = keyTable[index2];
if (key.equals(key2)) {
this.valueTable[index2] = value;
return;
}
int index3 = hash3(hashCode);
K key3 = keyTable[index3];
if (key.equals(key3)) {
this.valueTable[index3] = value;
return;
}
// Update key in the stash.
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (key.equals(keyTable[i])) {
this.valueTable[i] = value;
return;
}
}
// Check for empty buckets.
if (key1 == null) {
keyTable[index1] = key;
this.valueTable[index1] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
if (key2 == null) {
keyTable[index2] = key;
this.valueTable[index2] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
if (key3 == null) {
keyTable[index3] = key;
this.valueTable[index3] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
push(key, value, index1, key1, index2, key2, index3, key3);
}
/** Skips checks for existing keys. */
private void putResize (K key, int value) {
// Check for empty buckets.
int hashCode = key.hashCode();
int index1 = hashCode & this.mask;
K key1 = this.keyTable[index1];
if (key1 == null) {
this.keyTable[index1] = key;
this.valueTable[index1] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
int index2 = hash2(hashCode);
K key2 = this.keyTable[index2];
if (key2 == null) {
this.keyTable[index2] = key;
this.valueTable[index2] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
int index3 = hash3(hashCode);
K key3 = this.keyTable[index3];
if (key3 == null) {
this.keyTable[index3] = key;
this.valueTable[index3] = value;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
push(key, value, index1, key1, index2, key2, index3, key3);
}
private void push (K insertKey, int insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
K[] keyTable = this.keyTable;
int[] valueTable = this.valueTable;
int mask = this.mask;
// Push keys until an empty bucket is found.
K evictedKey;
int evictedValue;
int i = 0, pushIterations = this.pushIterations;
do {
// Replace the key and value for one of the hashes.
switch (MathUtils.randomInt(2)) {
case 0:
evictedKey = key1;
evictedValue = valueTable[index1];
keyTable[index1] = insertKey;
valueTable[index1] = insertValue;
break;
case 1:
evictedKey = key2;
evictedValue = valueTable[index2];
keyTable[index2] = insertKey;
valueTable[index2] = insertValue;
break;
default:
evictedKey = key3;
evictedValue = valueTable[index3];
keyTable[index3] = insertKey;
valueTable[index3] = insertValue;
break;
}
// If the evicted key hashes to an empty bucket, put it there and stop.
int hashCode = evictedKey.hashCode();
index1 = hashCode & mask;
key1 = keyTable[index1];
if (key1 == null) {
keyTable[index1] = evictedKey;
valueTable[index1] = evictedValue;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
index2 = hash2(hashCode);
key2 = keyTable[index2];
if (key2 == null) {
keyTable[index2] = evictedKey;
valueTable[index2] = evictedValue;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
index3 = hash3(hashCode);
key3 = keyTable[index3];
if (key3 == null) {
keyTable[index3] = evictedKey;
valueTable[index3] = evictedValue;
if (this.size++ >= this.threshold) {
resize(this.capacity << 1);
}
return;
}
if (++i == pushIterations) {
break;
}
insertKey = evictedKey;
insertValue = evictedValue;
} while (true);
putStash(evictedKey, evictedValue);
}
private void putStash (K key, int value) {
if (this.stashSize == this.stashCapacity) {
// Too many pushes occurred and the stash is full, increase the table size.
resize(this.capacity << 1);
put(key, value);
return;
}
// Store key in the stash.
int index = this.capacity + this.stashSize;
this.keyTable[index] = key;
this.valueTable[index] = value;
this.stashSize++;
this.size++;
}
/** @param defaultValue Returned if the key was not associated with a value. */
public int get (K key, int defaultValue) {
int hashCode = key.hashCode();
int index = hashCode & this.mask;
if (!key.equals(this.keyTable[index])) {
index = hash2(hashCode);
if (!key.equals(this.keyTable[index])) {
index = hash3(hashCode);
if (!key.equals(this.keyTable[index])) {
return getStash(key, defaultValue);
}
}
}
return this.valueTable[index];
}
private int getStash (K key, int defaultValue) {
K[] keyTable = this.keyTable;
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (key.equals(keyTable[i])) {
return this.valueTable[i];
}
}
return defaultValue;
}
/** Returns the key's current value and increments the stored value. If the key is not in the map, defaultValue + increment is
* put into the map. */
public int getAndIncrement (K key, int defaultValue, int increment) {
int hashCode = key.hashCode();
int index = hashCode & this.mask;
if (!key.equals(this.keyTable[index])) {
index = hash2(hashCode);
if (!key.equals(this.keyTable[index])) {
index = hash3(hashCode);
if (!key.equals(this.keyTable[index])) {
return getAndIncrementStash(key, defaultValue, increment);
}
}
}
int value = this.valueTable[index];
this.valueTable[index] = value + increment;
return value;
}
private int getAndIncrementStash (K key, int defaultValue, int increment) {
K[] keyTable = this.keyTable;
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (key.equals(keyTable[i])) {
int value = this.valueTable[i];
this.valueTable[i] = value + increment;
return value;
}
}
put(key, defaultValue + increment);
return defaultValue;
}
public int remove (K key, int defaultValue) {
int hashCode = key.hashCode();
int index = hashCode & this.mask;
if (key.equals(this.keyTable[index])) {
this.keyTable[index] = null;
int oldValue = this.valueTable[index];
this.size--;
return oldValue;
}
index = hash2(hashCode);
if (key.equals(this.keyTable[index])) {
this.keyTable[index] = null;
int oldValue = this.valueTable[index];
this.size--;
return oldValue;
}
index = hash3(hashCode);
if (key.equals(this.keyTable[index])) {
this.keyTable[index] = null;
int oldValue = this.valueTable[index];
this.size--;
return oldValue;
}
return removeStash(key, defaultValue);
}
int removeStash (K key, int defaultValue) {
K[] keyTable = this.keyTable;
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (key.equals(keyTable[i])) {
int oldValue = this.valueTable[i];
removeStashIndex(i);
this.size--;
return oldValue;
}
}
return defaultValue;
}
void removeStashIndex (int index) {
// If the removed location was not last, move the last tuple to the removed location.
this.stashSize--;
int lastIndex = this.capacity + this.stashSize;
if (index < lastIndex) {
this.keyTable[index] = this.keyTable[lastIndex];
this.valueTable[index] = this.valueTable[lastIndex];
}
}
/** Reduces the size of the backing arrays to be the specified capacity or less. If the capacity is already less, nothing is
* done. If the map contains more items than the specified capacity, the next highest power of two capacity is used instead. */
public void shrink (int maximumCapacity) {
if (maximumCapacity < 0) {
throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
}
if (this.size > maximumCapacity) {
maximumCapacity = this.size;
}
if (this.capacity <= maximumCapacity) {
return;
}
maximumCapacity = ObjectMap.nextPowerOfTwo(maximumCapacity);
resize(maximumCapacity);
}
/** Clears the map and reduces the size of the backing arrays to be the specified capacity if they are larger. */
public void clear (int maximumCapacity) {
if (this.capacity <= maximumCapacity) {
clear();
return;
}
this.size = 0;
resize(maximumCapacity);
}
public void clear () {
K[] keyTable = this.keyTable;
for (int i = this.capacity + this.stashSize; i-- > 0;) {
keyTable[i] = null;
}
this.size = 0;
this.stashSize = 0;
}
/** Returns true if the specified value is in the map. Note this traverses the entire map and compares every value, which may be
* an expensive operation. */
public boolean containsValue (int value) {
int[] valueTable = this.valueTable;
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (valueTable[i] == value) {
return true;
}
}
return false;
}
public boolean containsKey (K key) {
int hashCode = key.hashCode();
int index = hashCode & this.mask;
if (!key.equals(this.keyTable[index])) {
index = hash2(hashCode);
if (!key.equals(this.keyTable[index])) {
index = hash3(hashCode);
if (!key.equals(this.keyTable[index])) {
return containsKeyStash(key);
}
}
}
return true;
}
private boolean containsKeyStash (K key) {
K[] keyTable = this.keyTable;
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
if (key.equals(keyTable[i])) {
return true;
}
}
return false;
}
/** Returns the key for the specified value, or null if it is not in the map. Note this traverses the entire map and compares
* every value, which may be an expensive operation. */
public K findKey (int value) {
int[] valueTable = this.valueTable;
for (int i = this.capacity + this.stashSize; i-- > 0;) {
if (valueTable[i] == value) {
return this.keyTable[i];
}
}
return null;
}
/** Increases the size of the backing array to acommodate the specified number of additional items. Useful before adding many
* items to avoid multiple backing array resizes. */
public void ensureCapacity (int additionalCapacity) {
int sizeNeeded = this.size + additionalCapacity;
if (sizeNeeded >= this.threshold) {
resize(ObjectMap.nextPowerOfTwo((int)(sizeNeeded / this.loadFactor)));
}
}
@SuppressWarnings("unchecked")
private void resize (int newSize) {
int oldEndIndex = this.capacity + this.stashSize;
this.capacity = newSize;
this.threshold = (int)(newSize * this.loadFactor);
this.mask = newSize - 1;
this.hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
this.stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
this.pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
K[] oldKeyTable = this.keyTable;
int[] oldValueTable = this.valueTable;
this.keyTable = (K[])new Object[newSize + this.stashCapacity];
this.valueTable = new int[newSize + this.stashCapacity];
int oldSize = this.size;
this.size = 0;
this.stashSize = 0;
if (oldSize > 0) {
for (int i = 0; i < oldEndIndex; i++) {
K key = oldKeyTable[i];
if (key != null) {
putResize(key, oldValueTable[i]);
}
}
}
}
private int hash2 (int h) {
h *= PRIME2;
return (h ^ h >>> this.hashShift) & this.mask;
}
private int hash3 (int h) {
h *= PRIME3;
return (h ^ h >>> this.hashShift) & this.mask;
}
@Override
public String toString () {
if (this.size == 0) {
return "{}";
}
StringBuilder buffer = new StringBuilder(32);
buffer.append('{');
K[] keyTable = this.keyTable;
int[] valueTable = this.valueTable;
int i = keyTable.length;
while (i-- > 0) {
K key = keyTable[i];
if (key == null) {
continue;
}
buffer.append(key);
buffer.append('=');
buffer.append(valueTable[i]);
break;
}
while (i-- > 0) {
K key = keyTable[i];
if (key == null) {
continue;
}
buffer.append(", ");
buffer.append(key);
buffer.append('=');
buffer.append(valueTable[i]);
}
buffer.append('}');
return buffer.toString();
}
}

View File

@ -0,0 +1,239 @@
package dorkbox.util;
import java.io.IOException;
import java.util.Random;
import org.junit.Test;
public class MersenneTwisterFastTest {
@Test
public void mersenneTwisterTest() throws IOException {
int j;
MersenneTwisterFast r;
// CORRECTNESS TEST
// COMPARE WITH
// http://www.math.keio.ac.jp/matumoto/CODES/MT2002/mt19937ar.out
r = new MersenneTwisterFast(new int[] {0x123,0x234,0x345,0x456});
System.out.println("Output of MersenneTwisterFast with new (2002/1/26) seeding mechanism");
for (j = 0; j < 1000; j++) {
// first, convert the int from signed to "unsigned"
long l = r.nextInt();
if (l < 0) {
l += 4294967296L; // max int value
}
String s = String.valueOf(l);
while (s.length() < 10) {
s = " " + s; // buffer
}
System.out.print(s + " ");
if (j % 5 == 4) {
System.out.println();
}
}
// SPEED TEST
final long SEED = 4357;
int xx;
long ms;
System.out.println("\nTime to test grabbing 100000000 ints");
Random rr = new Random(SEED);
xx = 0;
ms = System.currentTimeMillis();
for (j = 0; j < 100000000; j++) {
xx += rr.nextInt();
}
System.out.println("java.util.Random: " + (System.currentTimeMillis() - ms) + " Ignore this: " + xx);
r = new MersenneTwisterFast(SEED);
ms = System.currentTimeMillis();
xx = 0;
for (j = 0; j < 100000000; j++) {
xx += r.nextInt();
}
System.out.println("Mersenne Twister Fast: " + (System.currentTimeMillis() - ms) + " Ignore this: "
+ xx);
// TEST TO COMPARE TYPE CONVERSION BETWEEN
// MersenneTwisterFast.java AND MersenneTwister.java
System.out.println("\nGrab the first 1000 booleans");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextBoolean() + " ");
if (j % 8 == 7) {
System.out.println();
}
}
if (!(j % 8 == 7)) {
System.out.println();
}
System.out.println("\nGrab 1000 booleans of increasing probability using nextBoolean(double)");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextBoolean(j / 999.0) + " ");
if (j % 8 == 7) {
System.out.println();
}
}
if (!(j % 8 == 7)) {
System.out.println();
}
System.out.println("\nGrab 1000 booleans of increasing probability using nextBoolean(float)");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextBoolean(j / 999.0f) + " ");
if (j % 8 == 7) {
System.out.println();
}
}
if (!(j % 8 == 7)) {
System.out.println();
}
byte[] bytes = new byte[1000];
System.out.println("\nGrab the first 1000 bytes using nextBytes");
r = new MersenneTwisterFast(SEED);
r.nextBytes(bytes);
for (j = 0; j < 1000; j++) {
System.out.print(bytes[j] + " ");
if (j % 16 == 15) {
System.out.println();
}
}
if (!(j % 16 == 15)) {
System.out.println();
}
byte b;
System.out.println("\nGrab the first 1000 bytes -- must be same as nextBytes");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print((b = r.nextByte()) + " ");
if (b != bytes[j]) {
System.out.print("BAD ");
}
if (j % 16 == 15) {
System.out.println();
}
}
if (!(j % 16 == 15)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 shorts");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextShort() + " ");
if (j % 8 == 7) {
System.out.println();
}
}
if (!(j % 8 == 7)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 ints");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextInt() + " ");
if (j % 4 == 3) {
System.out.println();
}
}
if (!(j % 4 == 3)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 ints of different sizes");
r = new MersenneTwisterFast(SEED);
int max = 1;
for (j = 0; j < 1000; j++) {
System.out.print(r.nextInt(max) + " ");
max *= 2;
if (max <= 0) {
max = 1;
}
if (j % 4 == 3) {
System.out.println();
}
}
if (!(j % 4 == 3)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 longs");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextLong() + " ");
if (j % 3 == 2) {
System.out.println();
}
}
if (!(j % 3 == 2)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 longs of different sizes");
r = new MersenneTwisterFast(SEED);
long max2 = 1;
for (j = 0; j < 1000; j++) {
System.out.print(r.nextLong(max2) + " ");
max2 *= 2;
if (max2 <= 0) {
max2 = 1;
}
if (j % 4 == 3) {
System.out.println();
}
}
if (!(j % 4 == 3)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 floats");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextFloat() + " ");
if (j % 4 == 3) {
System.out.println();
}
}
if (!(j % 4 == 3)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 doubles");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextDouble() + " ");
if (j % 3 == 2) {
System.out.println();
}
}
if (!(j % 3 == 2)) {
System.out.println();
}
System.out.println("\nGrab the first 1000 gaussian doubles");
r = new MersenneTwisterFast(SEED);
for (j = 0; j < 1000; j++) {
System.out.print(r.nextGaussian() + " ");
if (j % 3 == 2) {
System.out.println();
}
}
if (!(j % 3 == 2)) {
System.out.println();
}
}
}