Added MersenneTwisterFast (replaces java.util.Random), added missing licenses. Added field annonation exclusion serilaizer. Code polish
This commit is contained in:
parent
bbea13e42d
commit
142ab8a383
94
Dorkbox-Util/LICENSE.TXT
Normal file
94
Dorkbox-Util/LICENSE.TXT
Normal 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.
|
89
Dorkbox-Util/src/dorkbox/util/MathUtils.java
Normal file
89
Dorkbox-Util/src/dorkbox/util/MathUtils.java
Normal 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;
|
||||
}
|
||||
}
|
1233
Dorkbox-Util/src/dorkbox/util/MersenneTwisterFast.java
Normal file
1233
Dorkbox-Util/src/dorkbox/util/MersenneTwisterFast.java
Normal file
File diff suppressed because it is too large
Load Diff
384
Dorkbox-Util/src/dorkbox/util/primativeCollections/IntArray.java
Normal file
384
Dorkbox-Util/src/dorkbox/util/primativeCollections/IntArray.java
Normal 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();
|
||||
}
|
||||
}
|
870
Dorkbox-Util/src/dorkbox/util/primativeCollections/IntMap.java
Normal file
870
Dorkbox-Util/src/dorkbox/util/primativeCollections/IntMap.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
239
Dorkbox-Util/test/dorkbox/util/MersenneTwisterFastTest.java
Normal file
239
Dorkbox-Util/test/dorkbox/util/MersenneTwisterFastTest.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user