Added the rest of the collection utilities, updated to latest
This commit is contained in:
parent
dda9d08815
commit
22152200b9
652
src/dorkbox/util/collections/Array.java
Normal file
652
src/dorkbox/util/collections/Array.java
Normal file
|
@ -0,0 +1,652 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
|
||||
/** A resizable, ordered or unordered array of objects. 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 Array<T> implements Iterable<T> {
|
||||
/** Provides direct access to the underlying array. If the Array's generic type is not Object, this field may only be accessed
|
||||
* if the {@link Array#Array(boolean, int, Class)} constructor was used. */
|
||||
public T[] items;
|
||||
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
private ArrayIterable iterable;
|
||||
private Predicate.PredicateIterable<T> predicateIterable;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public Array () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public Array (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 Array (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = (T[])new Object[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new array with {@link #items} of the specified type.
|
||||
* @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 Array (boolean ordered, int capacity, Class arrayType) {
|
||||
this.ordered = ordered;
|
||||
items = (T[])java.lang.reflect.Array.newInstance(arrayType, capacity);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with {@link #items} of the specified type and a capacity of 16. */
|
||||
public Array (Class arrayType) {
|
||||
this(true, 16, arrayType);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The new array will have the same type of backing array
|
||||
* and will be ordered if the specified 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 Array (Array<? extends T> array) {
|
||||
this(array.ordered, array.size, array.items.getClass().getComponentType());
|
||||
size = array.size;
|
||||
System.arraycopy(array.items, 0, items, 0, size);
|
||||
}
|
||||
|
||||
/** Creates a new ordered array containing the elements in the specified array. The new array will have the same type of
|
||||
* backing array. The capacity is set to the number of elements, so any subsequent elements added will cause the backing array
|
||||
* to be grown. */
|
||||
public Array (T[] array) {
|
||||
this(true, array, 0, array.length);
|
||||
}
|
||||
|
||||
/** Creates a new array containing the elements in the specified array. The new array will have the same type of backing 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 Array (boolean ordered, T[] array, int start, int count) {
|
||||
this(ordered, count, (Class)array.getClass().getComponentType());
|
||||
size = count;
|
||||
System.arraycopy(array, start, items, 0, size);
|
||||
}
|
||||
|
||||
public void add (T value) {
|
||||
T[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (T value1, T value2) {
|
||||
T[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (T value1, T value2, T value3) {
|
||||
T[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (T value1, T value2, T value3, T value4) {
|
||||
T[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array, int start, int count) {
|
||||
if (start + count > array.size)
|
||||
throw new IllegalArgumentException("start + count must be <= size: " + start + " + " + count + " <= " + array.size);
|
||||
addAll((T[])array.items, start, count);
|
||||
}
|
||||
|
||||
public void addAll (T... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (T[] array, int start, int count) {
|
||||
T[] items = this.items;
|
||||
int sizeNeeded = size + count;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, start, items, size, count);
|
||||
size += count;
|
||||
}
|
||||
|
||||
public T get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, T value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void insert (int index, T value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
T[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
T[] items = this.items;
|
||||
T firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
/** Returns if this array contains value.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return true if array contains value, false if it doesn't */
|
||||
public boolean contains (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
int i = size - 1;
|
||||
if (identity || value == null) {
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
} else {
|
||||
while (i >= 0)
|
||||
if (value.equals(items[i--])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the index of first occurrence of value in the array, or -1 if no such value exists.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return An index of first occurrence of value in array or -1 if no such value exists */
|
||||
public int indexOf (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (value.equals(items[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Returns an index of last occurrence of value in array or -1 if no such value exists. Search is started from the end of an
|
||||
* array.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return An index of last occurrence of value in array or -1 if no such value exists */
|
||||
public int lastIndexOf (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
if (identity || value == null) {
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
} else {
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (value.equals(items[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Removes the first instance of the specified value in the array.
|
||||
* @param value May be null.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used.
|
||||
* @return true if value was found and removed, false otherwise */
|
||||
public boolean removeValue (T value, boolean identity) {
|
||||
T[] items = this.items;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (value.equals(items[i])) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public T removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
T[] items = this.items;
|
||||
T value = (T)items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
items[size] = null;
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
T[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @param identity True to use ==, false to use .equals().
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (Array<? extends T> array, boolean identity) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
T[] items = this.items;
|
||||
if (identity) {
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
T item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item == items[ii]) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
T item = array.get(i);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
if (item.equals(items[ii])) {
|
||||
removeIndex(ii);
|
||||
size--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return size != startSize;
|
||||
}
|
||||
|
||||
/** Removes and returns the last item. */
|
||||
public T pop () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
--size;
|
||||
T item = items[size];
|
||||
items[size] = null;
|
||||
return item;
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public T peek () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public T first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
T[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
items[i] = null;
|
||||
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.
|
||||
* @return {@link #items} */
|
||||
public T[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public T[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size null.
|
||||
* @return {@link #items} */
|
||||
public T[] setSize (int newSize) {
|
||||
truncate(newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Creates a new backing array with the specified size containing the current items. */
|
||||
protected T[] resize (int newSize) {
|
||||
T[] items = this.items;
|
||||
T[] newItems = (T[])java.lang.reflect.Array.newInstance(items.getClass().getComponentType(), newSize);
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
/** Sorts this array. The array elements must implement {@link Comparable}. This method is not thread safe (uses
|
||||
* {@link Sort#instance()}). */
|
||||
public void sort () {
|
||||
Sort.instance().sort(items, 0, size);
|
||||
}
|
||||
|
||||
/** Sorts the array. This method is not thread safe (uses {@link Sort#instance()}). */
|
||||
public void sort (Comparator<? super T> comparator) {
|
||||
Sort.instance().sort(items, comparator, 0, size);
|
||||
}
|
||||
|
||||
/** Selects the nth-lowest element from the Array according to Comparator ranking. This might partially sort the Array. The
|
||||
* array must have a size greater than 0, or a {@link RuntimeException} will be thrown.
|
||||
* @see Select
|
||||
* @param comparator used for comparison
|
||||
* @param kthLowest rank of desired object according to comparison, n is based on ordinal numbers, not array indices. for min
|
||||
* value use 1, for max value use size of array, using 0 results in runtime exception.
|
||||
* @return the value of the Nth lowest ranked object. */
|
||||
public T selectRanked (Comparator<T> comparator, int kthLowest) {
|
||||
if (kthLowest < 1) {
|
||||
throw new RuntimeException("nth_lowest must be greater than 0, 1 = first, 2 = second...");
|
||||
}
|
||||
return Select.instance().select(items, comparator, kthLowest, size);
|
||||
}
|
||||
|
||||
/** @see Array#selectRanked(java.util.Comparator, int)
|
||||
* @param comparator used for comparison
|
||||
* @param kthLowest rank of desired object according to comparison, n is based on ordinal numbers, not array indices. for min
|
||||
* value use 1, for max value use size of array, using 0 results in runtime exception.
|
||||
* @return the index of the Nth lowest ranked object. */
|
||||
public int selectRankedIndex (Comparator<T> comparator, int kthLowest) {
|
||||
if (kthLowest < 1) {
|
||||
throw new RuntimeException("nth_lowest must be greater than 0, 1 = first, 2 = second...");
|
||||
}
|
||||
return Select.instance().selectIndex(items, comparator, kthLowest, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
T[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
T temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
T[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
T temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns an iterator for the items in the array. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link ArrayIterator} constructor for nested or multithreaded iteration. */
|
||||
public Iterator<T> iterator () {
|
||||
if (iterable == null) iterable = new ArrayIterable(this);
|
||||
return iterable.iterator();
|
||||
}
|
||||
|
||||
/** Returns an iterable for the selected items in the array. Remove is supported, but not between hasNext() and next(). Note
|
||||
* that the same iterable instance is returned each time this method is called. Use the {@link Predicate.PredicateIterable}
|
||||
* constructor for nested or multithreaded iteration. */
|
||||
public Iterable<T> select (Predicate<T> predicate) {
|
||||
if (predicateIterable == null)
|
||||
predicateIterable = new Predicate.PredicateIterable<T>(this, predicate);
|
||||
else
|
||||
predicateIterable.set(this, predicate);
|
||||
return predicateIterable;
|
||||
}
|
||||
|
||||
/** 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 (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (size <= newSize) return;
|
||||
for (int i = newSize; i < size; i++)
|
||||
items[i] = null;
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or null if the array is empty. */
|
||||
public T random () {
|
||||
if (size == 0) return null;
|
||||
return items[RandomUtil.int_(0, size - 1)];
|
||||
}
|
||||
|
||||
/** Returns the items as an array. Note the array is typed, so the {@link #Array(Class)} constructor must have been used.
|
||||
* Otherwise use {@link #toArray(Class)} to specify the array type. */
|
||||
public T[] toArray () {
|
||||
return (T[])toArray(items.getClass().getComponentType());
|
||||
}
|
||||
|
||||
public <V> V[] toArray (Class<V> type) {
|
||||
V[] result = (V[])java.lang.reflect.Array.newInstance(type, size);
|
||||
System.arraycopy(items, 0, result, 0, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
Object[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
h *= 31;
|
||||
Object item = items[i];
|
||||
if (item != null) h += item.hashCode();
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof Array)) return false;
|
||||
Array array = (Array)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
Object[] items1 = this.items;
|
||||
Object[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Object o1 = items1[i];
|
||||
Object o2 = items2[i];
|
||||
if (!(o1 == null ? o2 == null : o1.equals(o2))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
T[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
T[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #Array(Class) */
|
||||
static public <T> Array<T> of (Class<T> arrayType) {
|
||||
return new Array(arrayType);
|
||||
}
|
||||
|
||||
/** @see #Array(boolean, int, Class) */
|
||||
static public <T> Array<T> of (boolean ordered, int capacity, Class<T> arrayType) {
|
||||
return new Array(ordered, capacity, arrayType);
|
||||
}
|
||||
|
||||
/** @see #Array(Object[]) */
|
||||
static public <T> Array<T> with (T... array) {
|
||||
return new Array(array);
|
||||
}
|
||||
|
||||
static public class ArrayIterator<T> implements Iterator<T>, Iterable<T> {
|
||||
private final Array<T> array;
|
||||
private final boolean allowRemove;
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
// ArrayIterable<T> iterable;
|
||||
|
||||
public ArrayIterator (Array<T> array) {
|
||||
this(array, true);
|
||||
}
|
||||
|
||||
public ArrayIterator (Array<T> array, boolean allowRemove) {
|
||||
this.array = array;
|
||||
this.allowRemove = allowRemove;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) {
|
||||
// System.out.println(iterable.lastAcquire);
|
||||
throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
}
|
||||
return index < array.size;
|
||||
}
|
||||
|
||||
public T next () {
|
||||
if (index >= array.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) {
|
||||
// System.out.println(iterable.lastAcquire);
|
||||
throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
}
|
||||
return array.items[index++];
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (!allowRemove) throw new RuntimeException("Remove not allowed.");
|
||||
index--;
|
||||
array.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public Iterator<T> iterator () {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
static public class ArrayIterable<T> implements Iterable<T> {
|
||||
private final Array<T> array;
|
||||
private final boolean allowRemove;
|
||||
private ArrayIterator iterator1, iterator2;
|
||||
|
||||
// java.io.StringWriter lastAcquire = new java.io.StringWriter();
|
||||
|
||||
public ArrayIterable (Array<T> array) {
|
||||
this(array, true);
|
||||
}
|
||||
|
||||
public ArrayIterable (Array<T> array, boolean allowRemove) {
|
||||
this.array = array;
|
||||
this.allowRemove = allowRemove;
|
||||
}
|
||||
|
||||
public Iterator<T> iterator () {
|
||||
// lastAcquire.getBuffer().setLength(0);
|
||||
// new Throwable().printStackTrace(new java.io.PrintWriter(lastAcquire));
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new ArrayIterator(array, allowRemove);
|
||||
iterator2 = new ArrayIterator(array, allowRemove);
|
||||
// iterator1.iterable = this;
|
||||
// iterator2.iterable = this;
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.index = 0;
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.index = 0;
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
}
|
||||
}
|
643
src/dorkbox/util/collections/ArrayMap.java
Normal file
643
src/dorkbox/util/collections/ArrayMap.java
Normal file
|
@ -0,0 +1,643 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
import dorkbox.util.collections.ObjectMap.Entry;
|
||||
|
||||
/** An ordered or unordered map of objects. This implementation uses arrays to store the keys and values, which means
|
||||
* {@link #getKey(Object, boolean) gets} do a comparison for each key in the map. This is slower than a typical hash map
|
||||
* implementation, but may be acceptable for small maps and has the benefits that keys and values can be accessed by index, which
|
||||
* makes iteration fast. Like {@link Array}, if ordered is false, 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 ArrayMap<K, V> implements Iterable<ObjectMap.Entry<K, V>> {
|
||||
public K[] keys;
|
||||
public V[] values;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values valuesIter1, valuesIter2;
|
||||
private Keys keysIter1, keysIter2;
|
||||
|
||||
/** Creates an ordered map with a capacity of 16. */
|
||||
public ArrayMap () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered map with the specified capacity. */
|
||||
public ArrayMap (int capacity) {
|
||||
this(true, capacity);
|
||||
}
|
||||
|
||||
/** @param ordered If false, methods that remove elements may change the order of other elements in the arrays, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing arrays to be grown. */
|
||||
public ArrayMap (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
keys = (K[])new Object[capacity];
|
||||
values = (V[])new Object[capacity];
|
||||
}
|
||||
|
||||
/** Creates a new map with {@link #keys} and {@link #values} of the specified type.
|
||||
* @param ordered If false, methods that remove elements may change the order of other elements in the arrays, which avoids a
|
||||
* memory copy.
|
||||
* @param capacity Any elements added beyond this will cause the backing arrays to be grown. */
|
||||
public ArrayMap (boolean ordered, int capacity, Class keyArrayType, Class valueArrayType) {
|
||||
this.ordered = ordered;
|
||||
keys = (K[])java.lang.reflect.Array.newInstance(keyArrayType, capacity);
|
||||
values = (V[])java.lang.reflect.Array.newInstance(valueArrayType, capacity);
|
||||
}
|
||||
|
||||
/** Creates an ordered map with {@link #keys} and {@link #values} of the specified type and a capacity of 16. */
|
||||
public ArrayMap (Class keyArrayType, Class valueArrayType) {
|
||||
this(false, 16, keyArrayType, valueArrayType);
|
||||
}
|
||||
|
||||
/** Creates a new map containing the elements in the specified map. The new map will have the same type of backing arrays and
|
||||
* will be ordered if the specified map is ordered. The capacity is set to the number of elements, so any subsequent elements
|
||||
* added will cause the backing arrays to be grown. */
|
||||
public ArrayMap (ArrayMap array) {
|
||||
this(array.ordered, array.size, array.keys.getClass().getComponentType(), array.values.getClass().getComponentType());
|
||||
size = array.size;
|
||||
System.arraycopy(array.keys, 0, keys, 0, size);
|
||||
System.arraycopy(array.values, 0, values, 0, size);
|
||||
}
|
||||
|
||||
public int put (K key, V value) {
|
||||
int index = indexOfKey(key);
|
||||
if (index == -1) {
|
||||
if (size == keys.length) resize(Math.max(8, (int)(size * 1.75f)));
|
||||
index = size++;
|
||||
}
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
return index;
|
||||
}
|
||||
|
||||
public int put (K key, V value, int index) {
|
||||
int existingIndex = indexOfKey(key);
|
||||
if (existingIndex != -1)
|
||||
removeIndex(existingIndex);
|
||||
else if (size == keys.length) //
|
||||
resize(Math.max(8, (int)(size * 1.75f)));
|
||||
System.arraycopy(keys, index, keys, index + 1, size - index);
|
||||
System.arraycopy(values, index, values, index + 1, size - index);
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
size++;
|
||||
return index;
|
||||
}
|
||||
|
||||
public void putAll (ArrayMap<? extends K, ? extends V> map) {
|
||||
putAll(map, 0, map.size);
|
||||
}
|
||||
|
||||
public void putAll (ArrayMap<? extends K, ? extends V> map, int offset, int length) {
|
||||
if (offset + length > map.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + map.size);
|
||||
int sizeNeeded = size + length - offset;
|
||||
if (sizeNeeded >= keys.length) resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(map.keys, offset, keys, size, length);
|
||||
System.arraycopy(map.values, offset, values, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
/** Returns the value for the specified key. Note this does a .equals() comparison of each key in reverse order until the
|
||||
* specified key is found. */
|
||||
public V get (K key) {
|
||||
Object[] keys = this.keys;
|
||||
int i = size - 1;
|
||||
if (key == null) {
|
||||
for (; i >= 0; i--)
|
||||
if (keys[i] == key) return values[i];
|
||||
} else {
|
||||
for (; i >= 0; i--)
|
||||
if (key.equals(keys[i])) return values[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns the key for the specified value. Note this does a comparison of each value in reverse order until the specified
|
||||
* value is found.
|
||||
* @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */
|
||||
public K getKey (V value, boolean identity) {
|
||||
Object[] values = this.values;
|
||||
int i = size - 1;
|
||||
if (identity || value == null) {
|
||||
for (; i >= 0; i--)
|
||||
if (values[i] == value) return keys[i];
|
||||
} else {
|
||||
for (; i >= 0; i--)
|
||||
if (value.equals(values[i])) return keys[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public K getKeyAt (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
return keys[index];
|
||||
}
|
||||
|
||||
public V getValueAt (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
return values[index];
|
||||
}
|
||||
|
||||
public K firstKey () {
|
||||
if (size == 0) throw new IllegalStateException("Map is empty.");
|
||||
return keys[0];
|
||||
}
|
||||
|
||||
public V firstValue () {
|
||||
if (size == 0) throw new IllegalStateException("Map is empty.");
|
||||
return values[0];
|
||||
}
|
||||
|
||||
public void setKey (int index, K key) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
keys[index] = key;
|
||||
}
|
||||
|
||||
public void setValue (int index, V value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
values[index] = value;
|
||||
}
|
||||
|
||||
public void insert (int index, K key, V value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
if (size == keys.length) resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered) {
|
||||
System.arraycopy(keys, index, keys, index + 1, size - index);
|
||||
System.arraycopy(values, index, values, index + 1, size - index);
|
||||
} else {
|
||||
keys[size] = keys[index];
|
||||
values[size] = values[index];
|
||||
}
|
||||
size++;
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
K[] keys = this.keys;
|
||||
int i = size - 1;
|
||||
if (key == null) {
|
||||
while (i >= 0)
|
||||
if (keys[i--] == key) return true;
|
||||
} else {
|
||||
while (i >= 0)
|
||||
if (key.equals(keys[i--])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */
|
||||
public boolean containsValue (V value, boolean identity) {
|
||||
V[] values = this.values;
|
||||
int i = size - 1;
|
||||
if (identity || value == null) {
|
||||
while (i >= 0)
|
||||
if (values[i--] == value) return true;
|
||||
} else {
|
||||
while (i >= 0)
|
||||
if (value.equals(values[i--])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOfKey (K key) {
|
||||
Object[] keys = this.keys;
|
||||
if (key == null) {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (keys[i] == key) return i;
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (key.equals(keys[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int indexOfValue (V value, boolean identity) {
|
||||
Object[] values = this.values;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (values[i] == value) return i;
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (value.equals(values[i])) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public V removeKey (K key) {
|
||||
Object[] keys = this.keys;
|
||||
if (key == null) {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (keys[i] == key) {
|
||||
V value = values[i];
|
||||
removeIndex(i);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (key.equals(keys[i])) {
|
||||
V value = values[i];
|
||||
removeIndex(i);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean removeValue (V value, boolean identity) {
|
||||
Object[] values = this.values;
|
||||
if (identity || value == null) {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (values[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (value.equals(values[i])) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the key/values pair at the specified index. */
|
||||
public void removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
Object[] keys = this.keys;
|
||||
size--;
|
||||
if (ordered) {
|
||||
System.arraycopy(keys, index + 1, keys, index, size - index);
|
||||
System.arraycopy(values, index + 1, values, index, size - index);
|
||||
} else {
|
||||
keys[index] = keys[size];
|
||||
values[index] = values[size];
|
||||
}
|
||||
keys[size] = null;
|
||||
values[size] = null;
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** Returns the last key. */
|
||||
public K peekKey () {
|
||||
return keys[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the last value. */
|
||||
public V peekValue () {
|
||||
return values[size - 1];
|
||||
}
|
||||
|
||||
/** 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 (keys.length <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
keys[i] = null;
|
||||
values[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
|
||||
/** Reduces the size of the backing arrays to the size of the actual number of entries. This is useful to release memory when
|
||||
* many items have been removed, or if it is known that more entries will not be added. */
|
||||
public void shrink () {
|
||||
if (keys.length == size) return;
|
||||
resize(size);
|
||||
}
|
||||
|
||||
/** Increases the size of the backing arrays to accommodate the specified number of additional entries. Useful before adding
|
||||
* many entries to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= keys.length) resize(Math.max(8, sizeNeeded));
|
||||
}
|
||||
|
||||
protected void resize (int newSize) {
|
||||
K[] newKeys = (K[])java.lang.reflect.Array.newInstance(keys.getClass().getComponentType(), newSize);
|
||||
System.arraycopy(keys, 0, newKeys, 0, Math.min(size, newKeys.length));
|
||||
this.keys = newKeys;
|
||||
|
||||
V[] newValues = (V[])java.lang.reflect.Array.newInstance(values.getClass().getComponentType(), newSize);
|
||||
System.arraycopy(values, 0, newValues, 0, Math.min(size, newValues.length));
|
||||
this.values = newValues;
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
K tempKey = keys[i];
|
||||
keys[i] = keys[ii];
|
||||
keys[ii] = tempKey;
|
||||
|
||||
V tempValue = values[i];
|
||||
values[i] = values[ii];
|
||||
values[ii] = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
K tempKey = keys[i];
|
||||
keys[i] = keys[ii];
|
||||
keys[ii] = tempKey;
|
||||
|
||||
V tempValue = values[i];
|
||||
values[i] = values[ii];
|
||||
values[ii] = tempValue;
|
||||
}
|
||||
}
|
||||
|
||||
/** Reduces the size of the arrays to the specified size. If the arrays are already smaller than the specified size, no action
|
||||
* is taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (size <= newSize) return;
|
||||
for (int i = newSize; i < size; i++) {
|
||||
keys[i] = null;
|
||||
values[i] = null;
|
||||
}
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
int h = 0;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
K key = keys[i];
|
||||
V value = values[i];
|
||||
if (key != null) h += key.hashCode() * 31;
|
||||
if (value != null) h += value.hashCode();
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ArrayMap)) return false;
|
||||
ArrayMap<K, V> other = (ArrayMap)obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
K key = keys[i];
|
||||
V value = values[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) return false;
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
K[] keys = this.keys;
|
||||
V[] values = this.values;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
buffer.append(keys[0]);
|
||||
buffer.append('=');
|
||||
buffer.append(values[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(keys[i]);
|
||||
buffer.append('=');
|
||||
buffer.append(values[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.index = 0;
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.index = 0;
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 (valuesIter1 == null) {
|
||||
valuesIter1 = new Values(this);
|
||||
valuesIter2 = new Values(this);
|
||||
}
|
||||
if (!valuesIter1.valid) {
|
||||
valuesIter1.index = 0;
|
||||
valuesIter1.valid = true;
|
||||
valuesIter2.valid = false;
|
||||
return valuesIter1;
|
||||
}
|
||||
valuesIter2.index = 0;
|
||||
valuesIter2.valid = true;
|
||||
valuesIter1.valid = false;
|
||||
return valuesIter2;
|
||||
}
|
||||
|
||||
/** 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<K> keys () {
|
||||
if (keysIter1 == null) {
|
||||
keysIter1 = new Keys(this);
|
||||
keysIter2 = new Keys(this);
|
||||
}
|
||||
if (!keysIter1.valid) {
|
||||
keysIter1.index = 0;
|
||||
keysIter1.valid = true;
|
||||
keysIter2.valid = false;
|
||||
return keysIter1;
|
||||
}
|
||||
keysIter2.index = 0;
|
||||
keysIter2.valid = true;
|
||||
keysIter1.valid = false;
|
||||
return keysIter2;
|
||||
}
|
||||
|
||||
static public class Entries<K, V> implements Iterable<Entry<K, V>>, Iterator<Entry<K, V>> {
|
||||
private final ArrayMap<K, V> map;
|
||||
Entry<K, V> entry = new ObjectMap.Entry();
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
public Entries (ArrayMap<K, V> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return index < map.size;
|
||||
}
|
||||
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry<K, V> next () {
|
||||
if (index >= map.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
entry.key = map.keys[index];
|
||||
entry.value = map.values[index++];
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
index--;
|
||||
map.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values<V> implements Iterable<V>, Iterator<V> {
|
||||
private final ArrayMap<Object, V> map;
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
public Values (ArrayMap<Object, V> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return index < map.size;
|
||||
}
|
||||
|
||||
public Iterator<V> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public V next () {
|
||||
if (index >= map.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return map.values[index++];
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
index--;
|
||||
map.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public Array<V> toArray () {
|
||||
return new Array(true, map.values, index, map.size - index);
|
||||
}
|
||||
|
||||
public Array<V> toArray (Array array) {
|
||||
array.addAll(map.values, index, map.size - index);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys<K> implements Iterable<K>, Iterator<K> {
|
||||
private final ArrayMap<K, Object> map;
|
||||
int index;
|
||||
boolean valid = true;
|
||||
|
||||
public Keys (ArrayMap<K, Object> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return index < map.size;
|
||||
}
|
||||
|
||||
public Iterator<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public K next () {
|
||||
if (index >= map.size) throw new NoSuchElementException(String.valueOf(index));
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return map.keys[index++];
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
index--;
|
||||
map.removeIndex(index);
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
public Array<K> toArray () {
|
||||
return new Array(true, map.keys, index, map.size - index);
|
||||
}
|
||||
|
||||
public Array<K> toArray (Array array) {
|
||||
array.addAll(map.keys, index, map.size - index);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
365
src/dorkbox/util/collections/BooleanArray.java
Normal file
365
src/dorkbox/util/collections/BooleanArray.java
Normal file
|
@ -0,0 +1,365 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** A resizable, ordered or unordered boolean array. Avoids the boxing that occurs with ArrayList<Boolean>. It is less memory
|
||||
* efficient than {@link BitSet}, except for very small sizes. It more CPU efficient than {@link BitSet}, except for very large
|
||||
* sizes or if BitSet functionality such as and, or, xor, etc are needed. 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 BooleanArray {
|
||||
public boolean[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public BooleanArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public BooleanArray (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 BooleanArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new boolean[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 BooleanArray (BooleanArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new boolean[size];
|
||||
System.arraycopy(array.items, 0, items, 0, 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 BooleanArray (boolean[] 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 BooleanArray (boolean ordered, boolean[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (boolean value) {
|
||||
boolean[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (boolean value1, boolean value2) {
|
||||
boolean[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (boolean value1, boolean value2, boolean value3) {
|
||||
boolean[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (boolean value1, boolean value2, boolean value3, boolean value4) {
|
||||
boolean[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (BooleanArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (BooleanArray 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 (boolean... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (boolean[] array, int offset, int length) {
|
||||
boolean[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public boolean get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, boolean value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void insert (int index, boolean value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
boolean[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
boolean[] items = this.items;
|
||||
boolean firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public boolean removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
boolean[] items = this.items;
|
||||
boolean value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
boolean[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (BooleanArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
boolean[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
boolean 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 boolean pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public boolean peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public boolean first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
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.
|
||||
* @return {@link #items} */
|
||||
public boolean[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public boolean[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public boolean[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected boolean[] resize (int newSize) {
|
||||
boolean[] newItems = new boolean[newSize];
|
||||
boolean[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
boolean[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
boolean temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
boolean[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
boolean temp = items[i];
|
||||
items[i] = items[ii];
|
||||
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 (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or false if the array is empty. */
|
||||
public boolean random () {
|
||||
if (size == 0) return false;
|
||||
return items[RandomUtil.int_(0, size - 1)];
|
||||
}
|
||||
|
||||
public boolean[] toArray () {
|
||||
boolean[] array = new boolean[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
boolean[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + (items[i] ? 1231 : 1237);
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof BooleanArray)) return false;
|
||||
BooleanArray array = (BooleanArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
boolean[] items1 = this.items;
|
||||
boolean[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
boolean[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
boolean[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #BooleanArray(boolean[]) */
|
||||
static public BooleanArray with (boolean... array) {
|
||||
return new BooleanArray(array);
|
||||
}
|
||||
}
|
410
src/dorkbox/util/collections/ByteArray.java
Normal file
410
src/dorkbox/util/collections/ByteArray.java
Normal file
|
@ -0,0 +1,410 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** A resizable, ordered or unordered byte array. Avoids the boxing that occurs with ArrayList<Byte>. 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 ByteArray {
|
||||
public byte[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public ByteArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public ByteArray (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 ByteArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new byte[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 ByteArray (ByteArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new byte[size];
|
||||
System.arraycopy(array.items, 0, items, 0, 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 ByteArray (byte[] 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 ByteArray (boolean ordered, byte[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (byte value) {
|
||||
byte[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (byte value1, byte value2) {
|
||||
byte[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (byte value1, byte value2, byte value3) {
|
||||
byte[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (byte value1, byte value2, byte value3, byte value4) {
|
||||
byte[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (ByteArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (ByteArray 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 (byte... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (byte[] array, int offset, int length) {
|
||||
byte[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public byte get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, byte value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, byte value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, byte value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, byte value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
byte[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
byte[] items = this.items;
|
||||
byte firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (byte value) {
|
||||
int i = size - 1;
|
||||
byte[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (byte value) {
|
||||
byte[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (byte value) {
|
||||
byte[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (byte value) {
|
||||
byte[] items = this.items;
|
||||
for (int i = 0, n = 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 >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
byte[] items = this.items;
|
||||
int value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
byte[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (ByteArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
byte[] 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 byte pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public byte peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public byte first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
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.
|
||||
* @return {@link #items} */
|
||||
public byte[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public byte[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public byte[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected byte[] resize (int newSize) {
|
||||
byte[] newItems = new byte[newSize];
|
||||
byte[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
byte[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
byte temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
byte[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
byte temp = items[i];
|
||||
items[i] = items[ii];
|
||||
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 (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public byte random () {
|
||||
if (size == 0) return 0;
|
||||
return items[RandomUtil.int_(0, size - 1)];
|
||||
}
|
||||
|
||||
public byte[] toArray () {
|
||||
byte[] array = new byte[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
byte[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + items[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof ByteArray)) return false;
|
||||
ByteArray array = (ByteArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
byte[] items1 = this.items;
|
||||
byte[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
byte[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
byte[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #ByteArray(byte[]) */
|
||||
static public ByteArray with (byte... array) {
|
||||
return new ByteArray(array);
|
||||
}
|
||||
}
|
410
src/dorkbox/util/collections/CharArray.java
Normal file
410
src/dorkbox/util/collections/CharArray.java
Normal file
|
@ -0,0 +1,410 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** A resizable, ordered or unordered char array. Avoids the boxing that occurs with ArrayList<Character>. 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 CharArray {
|
||||
public char[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public CharArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public CharArray (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 CharArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new char[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 CharArray (CharArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new char[size];
|
||||
System.arraycopy(array.items, 0, items, 0, 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 CharArray (char[] 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 CharArray (boolean ordered, char[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (char value) {
|
||||
char[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (char value1, char value2) {
|
||||
char[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (char value1, char value2, char value3) {
|
||||
char[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (char value1, char value2, char value3, char value4) {
|
||||
char[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (CharArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (CharArray 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 (char... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (char[] array, int offset, int length) {
|
||||
char[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public char get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, char value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, char value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, char value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, char value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
char[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
char[] items = this.items;
|
||||
char firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (char value) {
|
||||
int i = size - 1;
|
||||
char[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (char value) {
|
||||
char[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (char value) {
|
||||
char[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (char value) {
|
||||
char[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public char removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
char[] items = this.items;
|
||||
char value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
char[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (CharArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
char[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
char 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 char pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public char peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public char first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
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.
|
||||
* @return {@link #items} */
|
||||
public char[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public char[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public char[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected char[] resize (int newSize) {
|
||||
char[] newItems = new char[newSize];
|
||||
char[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
char[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
char temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
char[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
char temp = items[i];
|
||||
items[i] = items[ii];
|
||||
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 (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public char random () {
|
||||
if (size == 0) return 0;
|
||||
return items[RandomUtil.int_(0, size - 1)];
|
||||
}
|
||||
|
||||
public char[] toArray () {
|
||||
char[] array = new char[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
char[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + items[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof CharArray)) return false;
|
||||
CharArray array = (CharArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
char[] items1 = this.items;
|
||||
char[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
char[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
char[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #CharArray(char[]) */
|
||||
static public CharArray with (char... array) {
|
||||
return new CharArray(array);
|
||||
}
|
||||
}
|
805
src/dorkbox/util/collections/ComparableTimSort.java
Normal file
805
src/dorkbox/util/collections/ComparableTimSort.java
Normal file
|
@ -0,0 +1,805 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
/** This is a near duplicate of {@link TimSort}, modified for use with arrays of objects that implement {@link Comparable}, instead
|
||||
* of using explicit comparators.
|
||||
*
|
||||
* <p>
|
||||
* If you are using an optimizing VM, you may find that ComparableTimSort offers no performance benefit over TimSort in
|
||||
* conjunction with a comparator that simply returns {@code ((Comparable)first).compareTo(Second)}. If this is the case, you are
|
||||
* better off deleting ComparableTimSort to eliminate the code duplication. (See Arrays.java for details.) */
|
||||
class ComparableTimSort {
|
||||
/** This is the minimum sized sequence that will be merged. Shorter sequences will be lengthened by calling binarySort. If the
|
||||
* entire array is less than this length, no merges will be performed.
|
||||
*
|
||||
* This constant should be a power of two. It was 64 in Tim Peter's C implementation, but 32 was empirically determined to work
|
||||
* better in this implementation. In the unlikely event that you set this constant to be a number that's not a power of two,
|
||||
* you'll need to change the {@link #minRunLength} computation.
|
||||
*
|
||||
* If you decrease this constant, you must change the stackLen computation in the TimSort constructor, or you risk an
|
||||
* ArrayOutOfBounds exception. See listsort.txt for a discussion of the minimum stack length required as a function of the
|
||||
* length of the array being sorted and the minimum merge sequence length. */
|
||||
private static final int MIN_MERGE = 32;
|
||||
|
||||
/** The array being sorted. */
|
||||
private Object[] a;
|
||||
|
||||
/** When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times. */
|
||||
private static final int MIN_GALLOP = 7;
|
||||
|
||||
/** This controls when we get *into* galloping mode. It is initialized to MIN_GALLOP. The mergeLo and mergeHi methods nudge it
|
||||
* higher for random data, and lower for highly structured data. */
|
||||
private int minGallop = MIN_GALLOP;
|
||||
|
||||
/** Maximum initial size of tmp array, which is used for merging. The array can grow to accommodate demand.
|
||||
*
|
||||
* Unlike Tim's original C version, we do not allocate this much storage when sorting smaller arrays. This change was required
|
||||
* for performance. */
|
||||
private static final int INITIAL_TMP_STORAGE_LENGTH = 256;
|
||||
|
||||
/** Temp storage for merges. */
|
||||
private Object[] tmp;
|
||||
private int tmpCount;
|
||||
|
||||
/** A stack of pending runs yet to be merged. Run i starts at address base[i] and extends for len[i] elements. It's always true
|
||||
* (so long as the indices are in bounds) that:
|
||||
*
|
||||
* runBase[i] + runLen[i] == runBase[i + 1]
|
||||
*
|
||||
* so we could cut the storage for this, but it's a minor amount, and keeping all the info explicit simplifies the code. */
|
||||
private int stackSize = 0; // Number of pending runs on stack
|
||||
private final int[] runBase;
|
||||
private final int[] runLen;
|
||||
|
||||
/** Asserts have been placed in if-statements for performace. To enable them, set this field to true and enable them in VM with
|
||||
* a command line flag. If you modify this class, please do test the asserts! */
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
ComparableTimSort () {
|
||||
tmp = new Object[INITIAL_TMP_STORAGE_LENGTH];
|
||||
runBase = new int[40];
|
||||
runLen = new int[40];
|
||||
}
|
||||
|
||||
public void doSort (Object[] a, int lo, int hi) {
|
||||
stackSize = 0;
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi);
|
||||
binarySort(a, lo, hi, lo + initRunLen);
|
||||
return;
|
||||
}
|
||||
|
||||
this.a = a;
|
||||
tmpCount = 0;
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
pushRun(lo, runLen);
|
||||
mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
mergeForceCollapse();
|
||||
if (DEBUG) assert stackSize == 1;
|
||||
|
||||
this.a = null;
|
||||
Object[] tmp = this.tmp;
|
||||
for (int i = 0, n = tmpCount; i < n; i++)
|
||||
tmp[i] = null;
|
||||
}
|
||||
|
||||
/** Creates a TimSort instance to maintain the state of an ongoing sort.
|
||||
*
|
||||
* @param a the array to be sorted */
|
||||
private ComparableTimSort (Object[] a) {
|
||||
this.a = a;
|
||||
|
||||
// Allocate temp storage (which may be increased later if necessary)
|
||||
int len = a.length;
|
||||
Object[] newArray = new Object[len < 2 * INITIAL_TMP_STORAGE_LENGTH ? len >>> 1 : INITIAL_TMP_STORAGE_LENGTH];
|
||||
tmp = newArray;
|
||||
|
||||
/*
|
||||
* Allocate runs-to-be-merged stack (which cannot be expanded). The stack length requirements are described in listsort.txt.
|
||||
* The C version always uses the same stack length (85), but this was measured to be too expensive when sorting "mid-sized"
|
||||
* arrays (e.g., 100 elements) in Java. Therefore, we use smaller (but sufficiently large) stack lengths for smaller arrays.
|
||||
* The "magic numbers" in the computation below must be changed if MIN_MERGE is decreased. See the MIN_MERGE declaration
|
||||
* above for more information.
|
||||
*/
|
||||
int stackLen = (len < 120 ? 5 : len < 1542 ? 10 : len < 119151 ? 19 : 40);
|
||||
runBase = new int[stackLen];
|
||||
runLen = new int[stackLen];
|
||||
}
|
||||
|
||||
/*
|
||||
* The next two methods (which are package private and static) constitute the entire API of this class. Each of these methods
|
||||
* obeys the contract of the public method with the same signature in java.util.Arrays.
|
||||
*/
|
||||
|
||||
static void sort (Object[] a) {
|
||||
sort(a, 0, a.length);
|
||||
}
|
||||
|
||||
static void sort (Object[] a, int lo, int hi) {
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi);
|
||||
binarySort(a, lo, hi, lo + initRunLen);
|
||||
return;
|
||||
}
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
ComparableTimSort ts = new ComparableTimSort(a);
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
ts.pushRun(lo, runLen);
|
||||
ts.mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
ts.mergeForceCollapse();
|
||||
if (DEBUG) assert ts.stackSize == 1;
|
||||
}
|
||||
|
||||
/** Sorts the specified portion of the specified array using a binary insertion sort. This is the best method for sorting small
|
||||
* numbers of elements. It requires O(n log n) compares, but O(n^2) data movement (worst case).
|
||||
*
|
||||
* If the initial part of the specified range is already sorted, this method can take advantage of it: the method assumes that
|
||||
* the elements from index {@code lo}, inclusive, to {@code start}, exclusive are already sorted.
|
||||
*
|
||||
* @param a the array in which a range is to be sorted
|
||||
* @param lo the index of the first element in the range to be sorted
|
||||
* @param hi the index after the last element in the range to be sorted
|
||||
* @param start the index of the first element in the range that is not already known to be sorted (@code lo <= start <= hi} */
|
||||
@SuppressWarnings("fallthrough")
|
||||
private static void binarySort (Object[] a, int lo, int hi, int start) {
|
||||
if (DEBUG) assert lo <= start && start <= hi;
|
||||
if (start == lo) start++;
|
||||
for (; start < hi; start++) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Comparable<Object> pivot = (Comparable)a[start];
|
||||
|
||||
// Set left (and right) to the index where a[start] (pivot) belongs
|
||||
int left = lo;
|
||||
int right = start;
|
||||
if (DEBUG) assert left <= right;
|
||||
/*
|
||||
* Invariants: pivot >= all in [lo, left). pivot < all in [right, start).
|
||||
*/
|
||||
while (left < right) {
|
||||
int mid = (left + right) >>> 1;
|
||||
if (pivot.compareTo(a[mid]) < 0)
|
||||
right = mid;
|
||||
else
|
||||
left = mid + 1;
|
||||
}
|
||||
if (DEBUG) assert left == right;
|
||||
|
||||
/*
|
||||
* The invariants still hold: pivot >= all in [lo, left) and pivot < all in [left, start), so pivot belongs at left. Note
|
||||
* that if there are elements equal to pivot, left points to the first slot after them -- that's why this sort is stable.
|
||||
* Slide elements over to make room to make room for pivot.
|
||||
*/
|
||||
int n = start - left; // The number of elements to move
|
||||
// Switch is just an optimization for arraycopy in default case
|
||||
switch (n) {
|
||||
case 2:
|
||||
a[left + 2] = a[left + 1];
|
||||
case 1:
|
||||
a[left + 1] = a[left];
|
||||
break;
|
||||
default:
|
||||
System.arraycopy(a, left, a, left + 1, n);
|
||||
}
|
||||
a[left] = pivot;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the length of the run beginning at the specified position in the specified array and reverses the run if it is
|
||||
* descending (ensuring that the run will always be ascending when the method returns).
|
||||
*
|
||||
* A run is the longest ascending sequence with:
|
||||
*
|
||||
* a[lo] <= a[lo + 1] <= a[lo + 2] <= ...
|
||||
*
|
||||
* or the longest descending sequence with:
|
||||
*
|
||||
* a[lo] > a[lo + 1] > a[lo + 2] > ...
|
||||
*
|
||||
* For its intended use in a stable mergesort, the strictness of the definition of "descending" is needed so that the call can
|
||||
* safely reverse a descending sequence without violating stability.
|
||||
*
|
||||
* @param a the array in which a run is to be counted and possibly reversed
|
||||
* @param lo index of the first element in the run
|
||||
* @param hi index after the last element that may be contained in the run. It is required that @code{lo < hi}.
|
||||
* @return the length of the run beginning at the specified position in the specified array */
|
||||
@SuppressWarnings("unchecked")
|
||||
private static int countRunAndMakeAscending (Object[] a, int lo, int hi) {
|
||||
if (DEBUG) assert lo < hi;
|
||||
int runHi = lo + 1;
|
||||
if (runHi == hi) return 1;
|
||||
|
||||
// Find end of run, and reverse range if descending
|
||||
if (((Comparable)a[runHi++]).compareTo(a[lo]) < 0) { // Descending
|
||||
while (runHi < hi && ((Comparable)a[runHi]).compareTo(a[runHi - 1]) < 0)
|
||||
runHi++;
|
||||
reverseRange(a, lo, runHi);
|
||||
} else { // Ascending
|
||||
while (runHi < hi && ((Comparable)a[runHi]).compareTo(a[runHi - 1]) >= 0)
|
||||
runHi++;
|
||||
}
|
||||
|
||||
return runHi - lo;
|
||||
}
|
||||
|
||||
/** Reverse the specified range of the specified array.
|
||||
*
|
||||
* @param a the array in which a range is to be reversed
|
||||
* @param lo the index of the first element in the range to be reversed
|
||||
* @param hi the index after the last element in the range to be reversed */
|
||||
private static void reverseRange (Object[] a, int lo, int hi) {
|
||||
hi--;
|
||||
while (lo < hi) {
|
||||
Object t = a[lo];
|
||||
a[lo++] = a[hi];
|
||||
a[hi--] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the minimum acceptable run length for an array of the specified length. Natural runs shorter than this will be
|
||||
* extended with {@link #binarySort}.
|
||||
*
|
||||
* Roughly speaking, the computation is:
|
||||
*
|
||||
* If n < MIN_MERGE, return n (it's too small to bother with fancy stuff). Else if n is an exact power of 2, return
|
||||
* MIN_MERGE/2. Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k is close to, but strictly less than, an
|
||||
* exact power of 2.
|
||||
*
|
||||
* For the rationale, see listsort.txt.
|
||||
*
|
||||
* @param n the length of the array to be sorted
|
||||
* @return the length of the minimum run to be merged */
|
||||
private static int minRunLength (int n) {
|
||||
if (DEBUG) assert n >= 0;
|
||||
int r = 0; // Becomes 1 if any 1 bits are shifted off
|
||||
while (n >= MIN_MERGE) {
|
||||
r |= (n & 1);
|
||||
n >>= 1;
|
||||
}
|
||||
return n + r;
|
||||
}
|
||||
|
||||
/** Pushes the specified run onto the pending-run stack.
|
||||
*
|
||||
* @param runBase index of the first element in the run
|
||||
* @param runLen the number of elements in the run */
|
||||
private void pushRun (int runBase, int runLen) {
|
||||
this.runBase[stackSize] = runBase;
|
||||
this.runLen[stackSize] = runLen;
|
||||
stackSize++;
|
||||
}
|
||||
|
||||
/** Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished:
|
||||
*
|
||||
* 1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1] 2. runLen[i - 2] > runLen[i - 1]
|
||||
*
|
||||
* This method is called each time a new run is pushed onto the stack, so the invariants are guaranteed to hold for i <
|
||||
* stackSize upon entry to the method. */
|
||||
private void mergeCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if (n > 0 && runLen[n - 1] <= runLen[n] + runLen[n + 1]) {
|
||||
if (runLen[n - 1] < runLen[n + 1]) n--;
|
||||
mergeAt(n);
|
||||
} else if (runLen[n] <= runLen[n + 1]) {
|
||||
mergeAt(n);
|
||||
} else {
|
||||
break; // Invariant is established
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges all runs on the stack until only one remains. This method is called once, to complete the sort. */
|
||||
private void mergeForceCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if (n > 0 && runLen[n - 1] < runLen[n + 1]) n--;
|
||||
mergeAt(n);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges the two runs at stack indices i and i+1. Run i must be the penultimate or antepenultimate run on the stack. In other
|
||||
* words, i must be equal to stackSize-2 or stackSize-3.
|
||||
*
|
||||
* @param i stack index of the first of the two runs to merge */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mergeAt (int i) {
|
||||
if (DEBUG) assert stackSize >= 2;
|
||||
if (DEBUG) assert i >= 0;
|
||||
if (DEBUG) assert i == stackSize - 2 || i == stackSize - 3;
|
||||
|
||||
int base1 = runBase[i];
|
||||
int len1 = runLen[i];
|
||||
int base2 = runBase[i + 1];
|
||||
int len2 = runLen[i + 1];
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0;
|
||||
if (DEBUG) assert base1 + len1 == base2;
|
||||
|
||||
/*
|
||||
* Record the length of the combined runs; if i is the 3rd-last run now, also slide over the last run (which isn't involved
|
||||
* in this merge). The current run (i+1) goes away in any case.
|
||||
*/
|
||||
runLen[i] = len1 + len2;
|
||||
if (i == stackSize - 3) {
|
||||
runBase[i + 1] = runBase[i + 2];
|
||||
runLen[i + 1] = runLen[i + 2];
|
||||
}
|
||||
stackSize--;
|
||||
|
||||
/*
|
||||
* Find where the first element of run2 goes in run1. Prior elements in run1 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
int k = gallopRight((Comparable<Object>)a[base2], a, base1, len1, 0);
|
||||
if (DEBUG) assert k >= 0;
|
||||
base1 += k;
|
||||
len1 -= k;
|
||||
if (len1 == 0) return;
|
||||
|
||||
/*
|
||||
* Find where the last element of run1 goes in run2. Subsequent elements in run2 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
len2 = gallopLeft((Comparable<Object>)a[base1 + len1 - 1], a, base2, len2, len2 - 1);
|
||||
if (DEBUG) assert len2 >= 0;
|
||||
if (len2 == 0) return;
|
||||
|
||||
// Merge remaining runs, using tmp array with min(len1, len2) elements
|
||||
if (len1 <= len2)
|
||||
mergeLo(base1, len1, base2, len2);
|
||||
else
|
||||
mergeHi(base1, len1, base2, len2);
|
||||
}
|
||||
|
||||
/** Locates the position at which to insert the specified key into the specified sorted range; if the range contains an element
|
||||
* equal to key, returns the index of the leftmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], pretending that a[b - 1] is minus infinity and a[b
|
||||
* + n] is infinity. In other words, key belongs at index b + k; or in other words, the first k elements of a should
|
||||
* precede key, and the last n - k should follow it. */
|
||||
private static int gallopLeft (Comparable<Object> key, Object[] a, int base, int len, int hint) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
|
||||
int lastOfs = 0;
|
||||
int ofs = 1;
|
||||
if (key.compareTo(a[base + hint]) > 0) {
|
||||
// Gallop right until a[base+hint+lastOfs] < key <= a[base+hint+ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint + ofs]) > 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
} else { // key <= a[base + hint]
|
||||
// Gallop left until a[base+hint-ofs] < key <= a[base+hint-lastOfs]
|
||||
final int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint - ofs]) <= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[base+lastOfs] < key <= a[base+ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[base + lastOfs - 1] < key <= a[base + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (key.compareTo(a[base + m]) > 0)
|
||||
lastOfs = m + 1; // a[base + m] < key
|
||||
else
|
||||
ofs = m; // key <= a[base + m]
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[base + ofs - 1] < key <= a[base + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Like gallopLeft, except that if the range contains an element equal to key, gallopRight returns the index after the
|
||||
* rightmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] */
|
||||
private static int gallopRight (Comparable<Object> key, Object[] a, int base, int len, int hint) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
|
||||
int ofs = 1;
|
||||
int lastOfs = 0;
|
||||
if (key.compareTo(a[base + hint]) < 0) {
|
||||
// Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs]
|
||||
int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint - ofs]) < 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
} else { // a[b + hint] <= key
|
||||
// Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && key.compareTo(a[base + hint + ofs]) >= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (key.compareTo(a[base + m]) < 0)
|
||||
ofs = m; // key < a[b + m]
|
||||
else
|
||||
lastOfs = m + 1; // a[b + m] <= key
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[b + ofs - 1] <= key < a[b + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Merges two adjacent runs in place, in a stable fashion. The first element of the first run must be greater than the first
|
||||
* element of the second run (a[base1] > a[base2]), and the last element of the first run (a[base1 + len1-1]) must be greater
|
||||
* than all elements of the second run.
|
||||
*
|
||||
* For performance, this method should be called only when len1 <= len2; its twin, mergeHi should be called if len1 >= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mergeLo (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy first run into temp array
|
||||
Object[] a = this.a; // For performance
|
||||
Object[] tmp = ensureCapacity(len1);
|
||||
System.arraycopy(a, base1, tmp, 0, len1);
|
||||
|
||||
int cursor1 = 0; // Indexes into tmp array
|
||||
int cursor2 = base2; // Indexes int a
|
||||
int dest = base1; // Indexes int a
|
||||
|
||||
// Move first element of second run and deal with degenerate cases
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
return;
|
||||
}
|
||||
if (len1 == 1) {
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
return;
|
||||
}
|
||||
|
||||
int minGallop = this.minGallop; // Use local variable for performance
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run starts winning consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
if (((Comparable)a[cursor2]).compareTo(tmp[cursor1]) < 0) {
|
||||
a[dest++] = a[cursor2++];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 0) break outer;
|
||||
} else {
|
||||
a[dest++] = tmp[cursor1++];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
count1 = gallopRight((Comparable)a[cursor2], tmp, cursor1, len1, 0);
|
||||
if (count1 != 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, count1);
|
||||
dest += count1;
|
||||
cursor1 += count1;
|
||||
len1 -= count1;
|
||||
if (len1 <= 1) // len1 == 1 || len1 == 0
|
||||
break outer;
|
||||
}
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) break outer;
|
||||
|
||||
count2 = gallopLeft((Comparable)tmp[cursor1], a, cursor2, len2, 0);
|
||||
if (count2 != 0) {
|
||||
System.arraycopy(a, cursor2, a, dest, count2);
|
||||
dest += count2;
|
||||
cursor2 += count2;
|
||||
len2 -= count2;
|
||||
if (len2 == 0) break outer;
|
||||
}
|
||||
a[dest++] = tmp[cursor1++];
|
||||
if (--len1 == 1) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len1 == 1) {
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
} else if (len1 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len2 == 0;
|
||||
if (DEBUG) assert len1 > 1;
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Like mergeLo, except that this method should be called only if len1 >= len2; mergeLo should be called if len1 <= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void mergeHi (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy second run into temp array
|
||||
Object[] a = this.a; // For performance
|
||||
Object[] tmp = ensureCapacity(len2);
|
||||
System.arraycopy(a, base2, tmp, 0, len2);
|
||||
|
||||
int cursor1 = base1 + len1 - 1; // Indexes into a
|
||||
int cursor2 = len2 - 1; // Indexes into tmp array
|
||||
int dest = base2 + len2 - 1; // Indexes into a
|
||||
|
||||
// Move last element of first run and deal with degenerate cases
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) {
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
return;
|
||||
}
|
||||
if (len2 == 1) {
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2];
|
||||
return;
|
||||
}
|
||||
|
||||
int minGallop = this.minGallop; // Use local variable for performance
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run appears to win consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
if (((Comparable)tmp[cursor2]).compareTo(a[cursor1]) < 0) {
|
||||
a[dest--] = a[cursor1--];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 0) break outer;
|
||||
} else {
|
||||
a[dest--] = tmp[cursor2--];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
count1 = len1 - gallopRight((Comparable)tmp[cursor2], a, base1, len1, len1 - 1);
|
||||
if (count1 != 0) {
|
||||
dest -= count1;
|
||||
cursor1 -= count1;
|
||||
len1 -= count1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, count1);
|
||||
if (len1 == 0) break outer;
|
||||
}
|
||||
a[dest--] = tmp[cursor2--];
|
||||
if (--len2 == 1) break outer;
|
||||
|
||||
count2 = len2 - gallopLeft((Comparable)a[cursor1], tmp, 0, len2, len2 - 1);
|
||||
if (count2 != 0) {
|
||||
dest -= count2;
|
||||
cursor2 -= count2;
|
||||
len2 -= count2;
|
||||
System.arraycopy(tmp, cursor2 + 1, a, dest + 1, count2);
|
||||
if (len2 <= 1) break outer; // len2 == 1 || len2 == 0
|
||||
}
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len2 == 1) {
|
||||
if (DEBUG) assert len1 > 0;
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge
|
||||
} else if (len2 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len1 == 0;
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensures that the external array tmp has at least the specified number of elements, increasing its size if necessary. The
|
||||
* size increases exponentially to ensure amortized linear time complexity.
|
||||
*
|
||||
* @param minCapacity the minimum required capacity of the tmp array
|
||||
* @return tmp, whether or not it grew */
|
||||
private Object[] ensureCapacity (int minCapacity) {
|
||||
tmpCount = Math.max(tmpCount, minCapacity);
|
||||
if (tmp.length < minCapacity) {
|
||||
// Compute smallest power of 2 > minCapacity
|
||||
int newSize = minCapacity;
|
||||
newSize |= newSize >> 1;
|
||||
newSize |= newSize >> 2;
|
||||
newSize |= newSize >> 4;
|
||||
newSize |= newSize >> 8;
|
||||
newSize |= newSize >> 16;
|
||||
newSize++;
|
||||
|
||||
if (newSize < 0) // Not bloody likely!
|
||||
newSize = minCapacity;
|
||||
else
|
||||
newSize = Math.min(newSize, a.length >>> 1);
|
||||
|
||||
Object[] newArray = new Object[newSize];
|
||||
tmp = newArray;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/** Checks that fromIndex and toIndex are in range, and throws an appropriate exception if they aren't.
|
||||
*
|
||||
* @param arrayLen the length of the array
|
||||
* @param fromIndex the index of the first element of the range
|
||||
* @param toIndex the index after the last element of the range
|
||||
* @throws IllegalArgumentException if fromIndex > toIndex
|
||||
* @throws ArrayIndexOutOfBoundsException if fromIndex < 0 or toIndex > arrayLen */
|
||||
private static void rangeCheck (int arrayLen, int fromIndex, int toIndex) {
|
||||
if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
|
||||
if (fromIndex < 0) throw new ArrayIndexOutOfBoundsException(fromIndex);
|
||||
if (toIndex > arrayLen) throw new ArrayIndexOutOfBoundsException(toIndex);
|
||||
}
|
||||
}
|
425
src/dorkbox/util/collections/FloatArray.java
Normal file
425
src/dorkbox/util/collections/FloatArray.java
Normal file
|
@ -0,0 +1,425 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** A resizable, ordered or unordered float array. Avoids the boxing that occurs with ArrayList<Float>. 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 FloatArray {
|
||||
public float[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public FloatArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public FloatArray (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 FloatArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new float[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 FloatArray (FloatArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new float[size];
|
||||
System.arraycopy(array.items, 0, items, 0, 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 FloatArray (float[] 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 FloatArray (boolean ordered, float[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (float value) {
|
||||
float[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (float value1, float value2) {
|
||||
float[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (float value1, float value2, float value3) {
|
||||
float[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (float value1, float value2, float value3, float value4) {
|
||||
float[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (FloatArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (FloatArray 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 (float... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (float[] array, int offset, int length) {
|
||||
float[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public float get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, float value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, float value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, float value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, float value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
float[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
float[] items = this.items;
|
||||
float firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (float value) {
|
||||
int i = size - 1;
|
||||
float[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (float value) {
|
||||
float[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (char value) {
|
||||
float[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (float value) {
|
||||
float[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public float removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
float[] items = this.items;
|
||||
float value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
float[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (FloatArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
float[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
float 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 float pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public float peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public float first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
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.
|
||||
* @return {@link #items} */
|
||||
public float[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public float[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public float[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected float[] resize (int newSize) {
|
||||
float[] newItems = new float[newSize];
|
||||
float[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
float[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
float temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
float[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
float temp = items[i];
|
||||
items[i] = items[ii];
|
||||
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 (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public float random () {
|
||||
if (size == 0) return 0;
|
||||
return items[RandomUtil.int_(0, size - 1)];
|
||||
}
|
||||
|
||||
public float[] toArray () {
|
||||
float[] array = new float[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
float[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + Float.floatToIntBits(items[i]);
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof FloatArray)) return false;
|
||||
FloatArray array = (FloatArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
float[] items1 = this.items;
|
||||
float[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items1[i] != items2[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean equals (Object object, float epsilon) {
|
||||
if (object == this) return true;
|
||||
if (!(object instanceof FloatArray)) return false;
|
||||
FloatArray array = (FloatArray)object;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
if (!ordered) return false;
|
||||
if (!array.ordered) return false;
|
||||
float[] items1 = this.items;
|
||||
float[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (Math.abs(items1[i] - items2[i]) > epsilon) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
float[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
float[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #FloatArray(float[]) */
|
||||
static public FloatArray with (float... array) {
|
||||
return new FloatArray(array);
|
||||
}
|
||||
}
|
795
src/dorkbox/util/collections/IdentityMap.java
Normal file
795
src/dorkbox/util/collections/IdentityMap.java
Normal file
|
@ -0,0 +1,795 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** An unordered map that uses identity comparison for keys. This implementation is a cuckoo hash map using 3 hashes, random
|
||||
* walking, and a small stash for problematic keys. Null keys are not allowed. 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 */
|
||||
public class IdentityMap<K, V> implements Iterable<IdentityMap.Entry<K, V>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
K[] keyTable;
|
||||
V[] valueTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
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 51 and a load factor of 0.8. */
|
||||
public IdentityMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IdentityMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IdentityMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = (V[])new Object[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public IdentityMap (IdentityMap map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
public V put (K key, V value) {
|
||||
if (key == null) throw new IllegalArgumentException("key cannot be null.");
|
||||
K[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == key) {
|
||||
V oldValue = valueTable[index1];
|
||||
valueTable[index1] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == key) {
|
||||
V oldValue = valueTable[index2];
|
||||
valueTable[index2] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == key) {
|
||||
V oldValue = valueTable[index3];
|
||||
valueTable[index3] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
valueTable[i] = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (K key, V value) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (K insertKey, V insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
K evictedKey;
|
||||
V evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(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 = System.identityHashCode(evictedKey);
|
||||
index1 = hashCode & mask;
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (K key, V value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
public V get (K key) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(hashCode);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(hashCode);
|
||||
if (key != keyTable[index]) return getStash(key, null);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
public V get (K key, V defaultValue) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(hashCode);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(hashCode);
|
||||
if (key != keyTable[index]) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private V getStash (K key, V defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public V remove (K key) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
V removeStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
valueTable[lastIndex] = null;
|
||||
} else
|
||||
valueTable[index] = null;
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.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 (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;) {
|
||||
keyTable[i] = null;
|
||||
valueTable[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
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.
|
||||
* @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) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return true;
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return true;
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
int hashCode = System.identityHashCode(key);
|
||||
int index = hashCode & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(hashCode);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(hashCode);
|
||||
if (key != keyTable[index]) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) 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.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public K findKey (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return keyTable[i];
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return keyTable[i];
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return keyTable[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = keyTable;
|
||||
V[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = (V[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
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 >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
V value = valueTable[i];
|
||||
if (value != null) {
|
||||
h += value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof IdentityMap)) return false;
|
||||
IdentityMap<K, V> other = (IdentityMap) obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
V value = valueTable[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] 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();
|
||||
}
|
||||
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K, V> {
|
||||
public K key;
|
||||
public V value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private abstract class MapIterator<K, V, I> implements Iterable<I>, Iterator<I> {
|
||||
public boolean hasNext;
|
||||
|
||||
final IdentityMap<K, V> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (IdentityMap<K, V> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
map.valueTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries<K, V> extends MapIterator<K, V, Entry<K, V>> {
|
||||
private Entry<K, V> entry = new Entry();
|
||||
|
||||
public Entries (IdentityMap<K, V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry<K, V> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Iterator<Entry<K, V>> iterator () {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values<V> extends MapIterator<Object, V, V> {
|
||||
public Values (IdentityMap<?, V> map) {
|
||||
super((IdentityMap<Object, V>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** Adds the remaining values to the specified array. */
|
||||
public void toArray (Array<V> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys<K> extends MapIterator<K, Object, K> {
|
||||
public Keys (IdentityMap<K, ?> map) {
|
||||
super((IdentityMap<K, Object>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
public Iterator<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
/*
|
||||
* Copyright 2010 Mario Zechner (contact@badlogicgames.com), Nathan Sweet (admin@esotericsoftware.com)
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -12,13 +14,10 @@
|
|||
* 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.collections;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
@ -42,110 +41,129 @@ public class IntArray {
|
|||
}
|
||||
|
||||
/** @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. */
|
||||
* 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];
|
||||
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. */
|
||||
* 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);
|
||||
size = array.size;
|
||||
items = new int[size];
|
||||
System.arraycopy(array.items, 0, items, 0, 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. */
|
||||
* 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. */
|
||||
* 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);
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, 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;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (int value1, int value2) {
|
||||
int[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (int value1, int value2, int value3) {
|
||||
int[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (int value1, int value2, int value3, int value4) {
|
||||
int[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (IntArray array) {
|
||||
addAll(array, 0, array.size);
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (IntArray array, int offset, int length) {
|
||||
if (offset + length > array.size) {
|
||||
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) {
|
||||
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;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public int get (int index) {
|
||||
if (index >= this.size) {
|
||||
throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
}
|
||||
return this.items[index];
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, int value) {
|
||||
if (index >= this.size) {
|
||||
throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
}
|
||||
this.items[index] = value;
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, int value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, int value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, int value) {
|
||||
if (index > this.size) {
|
||||
throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
}
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
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++;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
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));
|
||||
}
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
int[] items = this.items;
|
||||
int firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
|
@ -153,39 +171,30 @@ public class IntArray {
|
|||
}
|
||||
|
||||
public boolean contains (int value) {
|
||||
int i = this.size - 1;
|
||||
int i = size - 1;
|
||||
int[] items = this.items;
|
||||
while (i >= 0) {
|
||||
if (items[i--] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
for (int i = 0, n = 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;
|
||||
}
|
||||
}
|
||||
for (int i = 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++) {
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
|
@ -196,22 +205,35 @@ public class IntArray {
|
|||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public int removeIndex (int index) {
|
||||
if (index >= this.size) {
|
||||
throw new IndexOutOfBoundsException(String.valueOf(index));
|
||||
}
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
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];
|
||||
}
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
int[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (IntArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
|
@ -231,136 +253,137 @@ public class IntArray {
|
|||
|
||||
/** Removes and returns the last item. */
|
||||
public int pop () {
|
||||
return this.items[--this.size];
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public int peek () {
|
||||
return this.items[this.size - 1];
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public int first () {
|
||||
if (this.size == 0) {
|
||||
throw new IllegalStateException("Array is empty.");
|
||||
}
|
||||
return this.items[0];
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
this.size = 0;
|
||||
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);
|
||||
/** 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.
|
||||
* @return {@link #items} */
|
||||
public int[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** 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} */
|
||||
/** Increases the size of the backing array to accommodate 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;
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public int[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return 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));
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(this.items, 0, this.size);
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
for (int i = 0, lastIndex = this.size - 1, n = this.size / 2; i < n; i++) {
|
||||
int[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
int temp = this.items[i];
|
||||
this.items[i] = this.items[ii];
|
||||
this.items[ii] = temp;
|
||||
int temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
for (int i = this.size - 1; i >= 0; i--) {
|
||||
int[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
int temp = this.items[i];
|
||||
this.items[i] = this.items[ii];
|
||||
this.items[ii] = temp;
|
||||
int temp = items[i];
|
||||
items[i] = items[ii];
|
||||
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. */
|
||||
* taken. */
|
||||
public void truncate (int newSize) {
|
||||
if (this.size > newSize) {
|
||||
this.size = newSize;
|
||||
}
|
||||
if (size > newSize) 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[RandomUtil.int_(0, this.size - 1)];
|
||||
if (size == 0) return 0;
|
||||
return items[RandomUtil.int_(0, size - 1)];
|
||||
}
|
||||
|
||||
public int[] toArray () {
|
||||
int[] array = new int[this.size];
|
||||
System.arraycopy(this.items, 0, array, 0, this.size);
|
||||
int[] array = new int[size];
|
||||
System.arraycopy(items, 0, array, 0, 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;
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
int[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + items[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof IntArray)) {
|
||||
return false;
|
||||
}
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
int[] items1 = this.items;
|
||||
int[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items[i] != array.items[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (this.size == 0) {
|
||||
return "[]";
|
||||
}
|
||||
if (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++) {
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
|
@ -369,16 +392,19 @@ public class IntArray {
|
|||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (this.size == 0) {
|
||||
return "";
|
||||
}
|
||||
if (size == 0) return "";
|
||||
int[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < this.size; i++) {
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #IntArray(int[]) */
|
||||
static public IntArray with (int... array) {
|
||||
return new IntArray(array);
|
||||
}
|
||||
}
|
||||
|
|
845
src/dorkbox/util/collections/IntFloatMap.java
Normal file
845
src/dorkbox/util/collections/IntFloatMap.java
Normal file
|
@ -0,0 +1,845 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** An unordered map where the keys are ints and values are floats. 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 IntFloatMap implements Iterable<IntFloatMap.Entry> {
|
||||
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;
|
||||
float[] valueTable;
|
||||
int capacity, stashSize;
|
||||
float 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 51 and a load factor of 0.8. */
|
||||
public IntFloatMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntFloatMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntFloatMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new int[capacity + stashCapacity];
|
||||
valueTable = new float[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public IntFloatMap (IntFloatMap map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
zeroValue = map.zeroValue;
|
||||
hasZeroValue = map.hasZeroValue;
|
||||
}
|
||||
|
||||
public void put (int key, float value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
if (!hasZeroValue) {
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key == key1) {
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key == key2) {
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key == key3) {
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (IntFloatMap map) {
|
||||
for (Entry entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (int key, float value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (int insertKey, float insertValue, int index1, int key1, int index2, int key2, int index3, int key3) {
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
int evictedKey;
|
||||
float evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(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 (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (int key, float value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** @param defaultValue Returned if the key was not associated with a value. */
|
||||
public float get (int key, float defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private float getStash (int key, float defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) return 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 float getAndIncrement (int key, float defaultValue, float increment) {
|
||||
if (key == 0) {
|
||||
if (hasZeroValue) {
|
||||
float value = zeroValue;
|
||||
zeroValue += increment;
|
||||
return value;
|
||||
} else {
|
||||
hasZeroValue = true;
|
||||
zeroValue = defaultValue + increment;
|
||||
++size;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
int index = key & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(key);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(key);
|
||||
if (key != keyTable[index]) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
float value = valueTable[index];
|
||||
valueTable[index] = value + increment;
|
||||
return value;
|
||||
}
|
||||
|
||||
private float getAndIncrementStash (int key, float defaultValue, float increment) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) {
|
||||
float value = valueTable[i];
|
||||
valueTable[i] = value + increment;
|
||||
return value;
|
||||
}
|
||||
put(key, defaultValue + increment);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public float remove (int key, float defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return zeroValue;
|
||||
}
|
||||
|
||||
int index = key & mask;
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key, defaultValue);
|
||||
}
|
||||
|
||||
float removeStash (int key, float defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
float oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.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 (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = EMPTY;
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
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 (float value) {
|
||||
if (hasZeroValue && zeroValue == value) return true;
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return true;
|
||||
return 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. */
|
||||
public boolean containsValue (float value, float epsilon) {
|
||||
if (hasZeroValue && Math.abs(zeroValue - value) <= epsilon) return true;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (Math.abs(valueTable[i] - value) <= epsilon) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (int key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == 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 int findKey (float value, int notFound) {
|
||||
if (hasZeroValue && zeroValue == value) return 0;
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return keyTable[i];
|
||||
return notFound;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
int[] oldKeyTable = keyTable;
|
||||
float[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = new int[newSize + stashCapacity];
|
||||
valueTable = new float[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 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 >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
if (hasZeroValue) {
|
||||
h += Float.floatToIntBits(zeroValue);
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
h += key * 31;
|
||||
|
||||
float value = valueTable[i];
|
||||
h += Float.floatToIntBits(value);
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof IntFloatMap)) return false;
|
||||
IntFloatMap other = (IntFloatMap)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
if (hasZeroValue && other.zeroValue != zeroValue) {
|
||||
return false;
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
float otherValue = other.get(key, 0f);
|
||||
if (otherValue == 0f && !other.containsKey(key)) return false;
|
||||
float value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
int[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
if (hasZeroValue) {
|
||||
buffer.append("0=");
|
||||
buffer.append(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();
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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 entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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 (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry {
|
||||
public int key;
|
||||
public float value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final IntFloatMap map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (IntFloatMap map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (map.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
int[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && map.hasZeroValue) {
|
||||
map.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = EMPTY;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries extends MapIterator implements Iterable<Entry>, Iterator<Entry> {
|
||||
private Entry entry = new Entry();
|
||||
|
||||
public Entries (IntFloatMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int[] keyTable = map.keyTable;
|
||||
if (nextIndex == INDEX_ZERO) {
|
||||
entry.key = 0;
|
||||
entry.value = map.zeroValue;
|
||||
} else {
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
}
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator {
|
||||
public Values (IntFloatMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public float next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
float value;
|
||||
if (nextIndex == INDEX_ZERO)
|
||||
value = map.zeroValue;
|
||||
else
|
||||
value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public FloatArray toArray () {
|
||||
FloatArray array = new FloatArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys extends MapIterator {
|
||||
public Keys (IntFloatMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int key = nextIndex == INDEX_ZERO ? 0 : map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
831
src/dorkbox/util/collections/IntIntMap.java
Normal file
831
src/dorkbox/util/collections/IntIntMap.java
Normal file
|
@ -0,0 +1,831 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** An unordered map where the keys and values are ints. This implementation is a cuckoo hash map using 3 hashes, random walking,
|
||||
* and a small stash for problematic keys. 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 IntIntMap implements Iterable<IntIntMap.Entry> {
|
||||
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, valueTable;
|
||||
int capacity, stashSize;
|
||||
int 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 51 and a load factor of 0.8. */
|
||||
public IntIntMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntIntMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntIntMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new int[capacity + stashCapacity];
|
||||
valueTable = new int[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public IntIntMap (IntIntMap map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
zeroValue = map.zeroValue;
|
||||
hasZeroValue = map.hasZeroValue;
|
||||
}
|
||||
|
||||
public void put (int key, int value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
if (!hasZeroValue) {
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key == key1) {
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key == key2) {
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key == key3) {
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (IntIntMap map) {
|
||||
for (Entry entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (int key, int value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (int insertKey, int insertValue, int index1, int key1, int index2, int key2, int index3, int key3) {
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
int evictedKey;
|
||||
int evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(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 (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (int key, int value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** @param defaultValue Returned if the key was not associated with a value. */
|
||||
public int get (int key, int defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private int getStash (int key, int defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) return 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 (int key, int defaultValue, int increment) {
|
||||
if (key == 0) {
|
||||
if (hasZeroValue) {
|
||||
int value = zeroValue;
|
||||
zeroValue += increment;
|
||||
return value;
|
||||
} else {
|
||||
hasZeroValue = true;
|
||||
zeroValue = defaultValue + increment;
|
||||
++size;
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
int index = key & mask;
|
||||
if (key != keyTable[index]) {
|
||||
index = hash2(key);
|
||||
if (key != keyTable[index]) {
|
||||
index = hash3(key);
|
||||
if (key != keyTable[index]) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
int value = valueTable[index];
|
||||
valueTable[index] = value + increment;
|
||||
return value;
|
||||
}
|
||||
|
||||
private int getAndIncrementStash (int key, int defaultValue, int increment) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == keyTable[i]) {
|
||||
int value = valueTable[i];
|
||||
valueTable[i] = value + increment;
|
||||
return value;
|
||||
}
|
||||
put(key, defaultValue + increment);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public int remove (int key, int defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return zeroValue;
|
||||
}
|
||||
|
||||
int index = key & mask;
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (key == keyTable[index]) {
|
||||
keyTable[index] = EMPTY;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key, defaultValue);
|
||||
}
|
||||
|
||||
int removeStash (int key, int defaultValue) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key == keyTable[i]) {
|
||||
int oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.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 (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = EMPTY;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
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. */
|
||||
public boolean containsValue (int value) {
|
||||
if (hasZeroValue && zeroValue == value) return true;
|
||||
int[] keyTable = this.keyTable, valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (int key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key == 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 int findKey (int value, int notFound) {
|
||||
if (hasZeroValue && zeroValue == value) return 0;
|
||||
int[] keyTable = this.keyTable, valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != 0 && valueTable[i] == value) return keyTable[i];
|
||||
return notFound;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
int[] oldKeyTable = keyTable;
|
||||
int[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = new int[newSize + stashCapacity];
|
||||
valueTable = new int[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 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 >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
if (hasZeroValue) {
|
||||
h += Float.floatToIntBits(zeroValue);
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
h += key * 31;
|
||||
|
||||
int value = valueTable[i];
|
||||
h += value;
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof IntIntMap)) return false;
|
||||
IntIntMap other = (IntIntMap)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
if (hasZeroValue && other.zeroValue != zeroValue) {
|
||||
return false;
|
||||
}
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
int key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
int otherValue = other.get(key, 0);
|
||||
if (otherValue == 0 && !other.containsKey(key)) return false;
|
||||
int value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
int[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
if (hasZeroValue) {
|
||||
buffer.append("0=");
|
||||
buffer.append(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();
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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 entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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 (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry {
|
||||
public int key;
|
||||
public int value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final IntIntMap map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (IntIntMap map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (map.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
int[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && map.hasZeroValue) {
|
||||
map.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = EMPTY;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries extends MapIterator implements Iterable<Entry>, Iterator<Entry> {
|
||||
private Entry entry = new Entry();
|
||||
|
||||
public Entries (IntIntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int[] keyTable = map.keyTable;
|
||||
if (nextIndex == INDEX_ZERO) {
|
||||
entry.key = 0;
|
||||
entry.value = map.zeroValue;
|
||||
} else {
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
}
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator {
|
||||
public Values (IntIntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int value;
|
||||
if (nextIndex == INDEX_ZERO)
|
||||
value = map.zeroValue;
|
||||
else
|
||||
value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys extends MapIterator {
|
||||
public Keys (IntIntMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int key = nextIndex == INDEX_ZERO ? 0 : map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
573
src/dorkbox/util/collections/IntSet.java
Normal file
573
src/dorkbox/util/collections/IntSet.java
Normal file
|
@ -0,0 +1,573 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** An unordered set that uses int keys. This implementation uses cuckoo hashing using 3 hashes, random walking, and a small stash
|
||||
* for problematic keys. No allocation is done except when growing the table size. <br>
|
||||
* <br>
|
||||
* This set performs very fast contains and remove (typically O(1), worst case O(log(n))). Add may be a bit slower, depending on
|
||||
* hash collisions. Load factors greater than 0.91 greatly increase the chances the set will have to rehash to the next higher POT
|
||||
* size.
|
||||
* @author Nathan Sweet */
|
||||
public class IntSet {
|
||||
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;
|
||||
int capacity, stashSize;
|
||||
boolean hasZeroValue;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private IntSetIterator iterator1, iterator2;
|
||||
|
||||
/** Creates a new set with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public IntSet () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntSet (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with the specified initial capacity and load factor. This set will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public IntSet (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new int[capacity + stashCapacity];
|
||||
}
|
||||
|
||||
/** Creates a new set identical to the specified set. */
|
||||
public IntSet (IntSet set) {
|
||||
this((int)Math.floor(set.capacity * set.loadFactor), set.loadFactor);
|
||||
stashSize = set.stashSize;
|
||||
System.arraycopy(set.keyTable, 0, keyTable, 0, set.keyTable.length);
|
||||
size = set.size;
|
||||
hasZeroValue = set.hasZeroValue;
|
||||
}
|
||||
|
||||
/** Returns true if the key was not already in the set. */
|
||||
public boolean add (int key) {
|
||||
if (key == 0) {
|
||||
if (hasZeroValue) return false;
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
return true;
|
||||
}
|
||||
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == key) return false;
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == key) return false;
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == key) return false;
|
||||
|
||||
// Find key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return false;
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addAll (IntArray array) {
|
||||
addAll(array.items, 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) {
|
||||
ensureCapacity(length);
|
||||
for (int i = offset, n = i + length; i < n; i++)
|
||||
add(array[i]);
|
||||
}
|
||||
|
||||
public void addAll (IntSet set) {
|
||||
ensureCapacity(set.size);
|
||||
IntSetIterator iterator = set.iterator();
|
||||
while (iterator.hasNext)
|
||||
add(iterator.next());
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void addResize (int key) {
|
||||
if (key == 0) {
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = key & mask;
|
||||
int key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
int key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
int key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (int insertKey, int index1, int key1, int index2, int key2, int index3, int key3) {
|
||||
int[] keyTable = this.keyTable;
|
||||
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
int evictedKey;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
keyTable[index1] = insertKey;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
keyTable[index2] = insertKey;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
keyTable[index3] = insertKey;
|
||||
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;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
} while (true);
|
||||
|
||||
addStash(evictedKey);
|
||||
}
|
||||
|
||||
private void addStash (int key) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
addResize(key);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** Returns true if the key was removed. */
|
||||
public boolean remove (int key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return false;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
int index = key & mask;
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
boolean removeStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) keyTable[index] = keyTable[lastIndex];
|
||||
}
|
||||
|
||||
/** Returns true if the set is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 set 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the set and reduces the size of the backing arrays to be the specified capacity if they are larger. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = EMPTY;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
hasZeroValue = false;
|
||||
}
|
||||
|
||||
public boolean contains (int key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = key & mask;
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (int key) {
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int first () {
|
||||
if (hasZeroValue) return 0;
|
||||
int[] keyTable = this.keyTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != EMPTY) return keyTable[i];
|
||||
throw new IllegalStateException("IntSet is empty.");
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
int[] oldKeyTable = keyTable;
|
||||
|
||||
keyTable = new int[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
int key = oldKeyTable[i];
|
||||
if (key != EMPTY) addResize(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != EMPTY) h += keyTable[i];
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (!(obj instanceof IntSet)) return false;
|
||||
IntSet other = (IntSet)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != EMPTY && !other.contains(keyTable[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
int[] keyTable = this.keyTable;
|
||||
int i = keyTable.length;
|
||||
if (hasZeroValue)
|
||||
buffer.append("0");
|
||||
else {
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (i-- > 0) {
|
||||
int key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the set. Remove is supported. Note that the same iterator instance is returned each time
|
||||
* this method is called. Use the {@link IntSetIterator} constructor for nested or multithreaded iteration. */
|
||||
public IntSetIterator iterator () {
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new IntSetIterator(this);
|
||||
iterator2 = new IntSetIterator(this);
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.reset();
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.reset();
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
|
||||
static public IntSet with (int... array) {
|
||||
IntSet set = new IntSet();
|
||||
set.addAll(array);
|
||||
return set;
|
||||
}
|
||||
|
||||
static public class IntSetIterator {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final IntSet set;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public IntSetIterator (IntSet set) {
|
||||
this.set = set;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (set.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
int[] keyTable = set.keyTable;
|
||||
for (int n = set.capacity + set.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && set.hasZeroValue) {
|
||||
set.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= set.capacity) {
|
||||
set.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
set.keyTable[currentIndex] = EMPTY;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
set.size--;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int key = nextIndex == INDEX_ZERO ? 0 : set.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, set.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
410
src/dorkbox/util/collections/LongArray.java
Normal file
410
src/dorkbox/util/collections/LongArray.java
Normal file
|
@ -0,0 +1,410 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** A resizable, ordered or unordered long array. Avoids the boxing that occurs with ArrayList<Long>. 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 LongArray {
|
||||
public long[] items;
|
||||
public int size;
|
||||
public boolean ordered;
|
||||
|
||||
/** Creates an ordered array with a capacity of 16. */
|
||||
public LongArray () {
|
||||
this(true, 16);
|
||||
}
|
||||
|
||||
/** Creates an ordered array with the specified capacity. */
|
||||
public LongArray (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 LongArray (boolean ordered, int capacity) {
|
||||
this.ordered = ordered;
|
||||
items = new long[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 LongArray (LongArray array) {
|
||||
this.ordered = array.ordered;
|
||||
size = array.size;
|
||||
items = new long[size];
|
||||
System.arraycopy(array.items, 0, items, 0, 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 LongArray (long[] 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 LongArray (boolean ordered, long[] array, int startIndex, int count) {
|
||||
this(ordered, count);
|
||||
size = count;
|
||||
System.arraycopy(array, startIndex, items, 0, count);
|
||||
}
|
||||
|
||||
public void add (long value) {
|
||||
long[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size++] = value;
|
||||
}
|
||||
|
||||
public void add (long value1, long value2) {
|
||||
long[] items = this.items;
|
||||
if (size + 1 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
size += 2;
|
||||
}
|
||||
|
||||
public void add (long value1, long value2, long value3) {
|
||||
long[] items = this.items;
|
||||
if (size + 2 >= items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
size += 3;
|
||||
}
|
||||
|
||||
public void add (long value1, long value2, long value3, long value4) {
|
||||
long[] items = this.items;
|
||||
if (size + 3 >= items.length) items = resize(Math.max(8, (int)(size * 1.8f))); // 1.75 isn't enough when size=5.
|
||||
items[size] = value1;
|
||||
items[size + 1] = value2;
|
||||
items[size + 2] = value3;
|
||||
items[size + 3] = value4;
|
||||
size += 4;
|
||||
}
|
||||
|
||||
public void addAll (LongArray array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (LongArray 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 (long... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (long[] array, int offset, int length) {
|
||||
long[] items = this.items;
|
||||
int sizeNeeded = size + length;
|
||||
if (sizeNeeded > items.length) items = resize(Math.max(8, (int)(sizeNeeded * 1.75f)));
|
||||
System.arraycopy(array, offset, items, size, length);
|
||||
size += length;
|
||||
}
|
||||
|
||||
public long get (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
public void set (int index, long value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void incr (int index, long value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] += value;
|
||||
}
|
||||
|
||||
public void mul (int index, long value) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
items[index] *= value;
|
||||
}
|
||||
|
||||
public void insert (int index, long value) {
|
||||
if (index > size) throw new IndexOutOfBoundsException("index can't be > size: " + index + " > " + size);
|
||||
long[] items = this.items;
|
||||
if (size == items.length) items = resize(Math.max(8, (int)(size * 1.75f)));
|
||||
if (ordered)
|
||||
System.arraycopy(items, index, items, index + 1, size - index);
|
||||
else
|
||||
items[size] = items[index];
|
||||
size++;
|
||||
items[index] = value;
|
||||
}
|
||||
|
||||
public void swap (int first, int second) {
|
||||
if (first >= size) throw new IndexOutOfBoundsException("first can't be >= size: " + first + " >= " + size);
|
||||
if (second >= size) throw new IndexOutOfBoundsException("second can't be >= size: " + second + " >= " + size);
|
||||
long[] items = this.items;
|
||||
long firstValue = items[first];
|
||||
items[first] = items[second];
|
||||
items[second] = firstValue;
|
||||
}
|
||||
|
||||
public boolean contains (long value) {
|
||||
int i = size - 1;
|
||||
long[] items = this.items;
|
||||
while (i >= 0)
|
||||
if (items[i--] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public int indexOf (long value) {
|
||||
long[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int lastIndexOf (char value) {
|
||||
long[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--)
|
||||
if (items[i] == value) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean removeValue (long value) {
|
||||
long[] items = this.items;
|
||||
for (int i = 0, n = size; i < n; i++) {
|
||||
if (items[i] == value) {
|
||||
removeIndex(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Removes and returns the item at the specified index. */
|
||||
public long removeIndex (int index) {
|
||||
if (index >= size) throw new IndexOutOfBoundsException("index can't be >= size: " + index + " >= " + size);
|
||||
long[] items = this.items;
|
||||
long value = items[index];
|
||||
size--;
|
||||
if (ordered)
|
||||
System.arraycopy(items, index + 1, items, index, size - index);
|
||||
else
|
||||
items[index] = items[size];
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes the items between the specified indices, inclusive. */
|
||||
public void removeRange (int start, int end) {
|
||||
if (end >= size) throw new IndexOutOfBoundsException("end can't be >= size: " + end + " >= " + size);
|
||||
if (start > end) throw new IndexOutOfBoundsException("start can't be > end: " + start + " > " + end);
|
||||
long[] items = this.items;
|
||||
int count = end - start + 1;
|
||||
if (ordered)
|
||||
System.arraycopy(items, start + count, items, start, size - (start + count));
|
||||
else {
|
||||
int lastIndex = this.size - 1;
|
||||
for (int i = 0; i < count; i++)
|
||||
items[start + i] = items[lastIndex - i];
|
||||
}
|
||||
size -= count;
|
||||
}
|
||||
|
||||
/** Removes from this array all of elements contained in the specified array.
|
||||
* @return true if this array was modified. */
|
||||
public boolean removeAll (LongArray array) {
|
||||
int size = this.size;
|
||||
int startSize = size;
|
||||
long[] items = this.items;
|
||||
for (int i = 0, n = array.size; i < n; i++) {
|
||||
long 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 long pop () {
|
||||
return items[--size];
|
||||
}
|
||||
|
||||
/** Returns the last item. */
|
||||
public long peek () {
|
||||
return items[size - 1];
|
||||
}
|
||||
|
||||
/** Returns the first item. */
|
||||
public long first () {
|
||||
if (size == 0) throw new IllegalStateException("Array is empty.");
|
||||
return items[0];
|
||||
}
|
||||
|
||||
/** Returns true if the array is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
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.
|
||||
* @return {@link #items} */
|
||||
public long[] shrink () {
|
||||
if (items.length != size) resize(size);
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes.
|
||||
* @return {@link #items} */
|
||||
public long[] ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded > items.length) resize(Math.max(8, sizeNeeded));
|
||||
return items;
|
||||
}
|
||||
|
||||
/** Sets the array size, leaving any values beyond the current size undefined.
|
||||
* @return {@link #items} */
|
||||
public long[] setSize (int newSize) {
|
||||
if (newSize < 0) throw new IllegalArgumentException("newSize must be >= 0: " + newSize);
|
||||
if (newSize > items.length) resize(Math.max(8, newSize));
|
||||
size = newSize;
|
||||
return items;
|
||||
}
|
||||
|
||||
protected long[] resize (int newSize) {
|
||||
long[] newItems = new long[newSize];
|
||||
long[] items = this.items;
|
||||
System.arraycopy(items, 0, newItems, 0, Math.min(size, newItems.length));
|
||||
this.items = newItems;
|
||||
return newItems;
|
||||
}
|
||||
|
||||
public void sort () {
|
||||
Arrays.sort(items, 0, size);
|
||||
}
|
||||
|
||||
public void reverse () {
|
||||
long[] items = this.items;
|
||||
for (int i = 0, lastIndex = size - 1, n = size / 2; i < n; i++) {
|
||||
int ii = lastIndex - i;
|
||||
long temp = items[i];
|
||||
items[i] = items[ii];
|
||||
items[ii] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void shuffle () {
|
||||
long[] items = this.items;
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
int ii = RandomUtil.int_(i);
|
||||
long temp = items[i];
|
||||
items[i] = items[ii];
|
||||
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 (size > newSize) size = newSize;
|
||||
}
|
||||
|
||||
/** Returns a random item from the array, or zero if the array is empty. */
|
||||
public long random () {
|
||||
if (size == 0) return 0;
|
||||
return items[RandomUtil.int_(0, size - 1)];
|
||||
}
|
||||
|
||||
public long[] toArray () {
|
||||
long[] array = new long[size];
|
||||
System.arraycopy(items, 0, array, 0, size);
|
||||
return array;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
if (!ordered) return super.hashCode();
|
||||
long[] items = this.items;
|
||||
int h = 1;
|
||||
for (int i = 0, n = size; i < n; i++)
|
||||
h = h * 31 + (int)(items[i] ^ (items[i] >>> 32));
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object object) {
|
||||
if (object == this) return true;
|
||||
if (!ordered) return false;
|
||||
if (!(object instanceof LongArray)) return false;
|
||||
LongArray array = (LongArray)object;
|
||||
if (!array.ordered) return false;
|
||||
int n = size;
|
||||
if (n != array.size) return false;
|
||||
long[] items1 = this.items;
|
||||
long[] items2 = array.items;
|
||||
for (int i = 0; i < n; i++)
|
||||
if (items[i] != array.items[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
long[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
long[] items = this.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(separator);
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** @see #LongArray(long[]) */
|
||||
static public LongArray with (long... array) {
|
||||
return new LongArray(array);
|
||||
}
|
||||
}
|
857
src/dorkbox/util/collections/LongMap.java
Normal file
857
src/dorkbox/util/collections/LongMap.java
Normal file
|
@ -0,0 +1,857 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** An unordered map that uses long 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 */
|
||||
public class LongMap<V> implements Iterable<LongMap.Entry<V>> {
|
||||
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;
|
||||
|
||||
long[] 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 51 and a load factor of 0.8. */
|
||||
public LongMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public LongMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public LongMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 63 - Long.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = new long[capacity + stashCapacity];
|
||||
valueTable = (V[])new Object[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public LongMap (LongMap<? extends V> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
zeroValue = map.zeroValue;
|
||||
hasZeroValue = map.hasZeroValue;
|
||||
}
|
||||
|
||||
public V put (long key, V value) {
|
||||
if (key == 0) {
|
||||
V oldValue = zeroValue;
|
||||
zeroValue = value;
|
||||
if (!hasZeroValue) {
|
||||
hasZeroValue = true;
|
||||
size++;
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
long[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int index1 = (int)(key & mask);
|
||||
long key1 = keyTable[index1];
|
||||
if (key1 == key) {
|
||||
V oldValue = valueTable[index1];
|
||||
valueTable[index1] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
long key2 = keyTable[index2];
|
||||
if (key2 == key) {
|
||||
V oldValue = valueTable[index2];
|
||||
valueTable[index2] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
long key3 = keyTable[index3];
|
||||
if (key3 == key) {
|
||||
V oldValue = valueTable[index3];
|
||||
valueTable[index3] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
valueTable[i] = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void putAll (LongMap<? extends V> map) {
|
||||
for (Entry<? extends V> entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (long key, V value) {
|
||||
if (key == 0) {
|
||||
zeroValue = value;
|
||||
hasZeroValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
int index1 = (int)(key & mask);
|
||||
long key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(key);
|
||||
long key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(key);
|
||||
long key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (long insertKey, V insertValue, int index1, long key1, int index2, long key2, int index3, long key3) {
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
long evictedKey;
|
||||
V evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(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 = (int)(evictedKey & mask);
|
||||
key1 = keyTable[index1];
|
||||
if (key1 == EMPTY) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(evictedKey);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == EMPTY) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(evictedKey);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == EMPTY) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (long key, V value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
public V get (long key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return null;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, null);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
public V get (long key, V defaultValue) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return defaultValue;
|
||||
return zeroValue;
|
||||
}
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private V getStash (long key, V defaultValue) {
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (keyTable[i] == key) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public V remove (long key) {
|
||||
if (key == 0) {
|
||||
if (!hasZeroValue) return null;
|
||||
V oldValue = zeroValue;
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(key);
|
||||
if (keyTable[index] == key) {
|
||||
keyTable[index] = EMPTY;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
V removeStash (long key) {
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (keyTable[i] == key) {
|
||||
V oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
valueTable[lastIndex] = null;
|
||||
} else
|
||||
valueTable[index] = null;
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.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 (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
zeroValue = null;
|
||||
hasZeroValue = false;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;) {
|
||||
keyTable[i] = EMPTY;
|
||||
valueTable[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
zeroValue = null;
|
||||
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. */
|
||||
public boolean containsValue (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
if (hasZeroValue && zeroValue == null) return true;
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != EMPTY && valueTable[i] == null) return true;
|
||||
} else if (identity) {
|
||||
if (value == zeroValue) return true;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return true;
|
||||
} else {
|
||||
if (hasZeroValue && value.equals(zeroValue)) return true;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (long key) {
|
||||
if (key == 0) return hasZeroValue;
|
||||
int index = (int)(key & mask);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash2(key);
|
||||
if (keyTable[index] != key) {
|
||||
index = hash3(key);
|
||||
if (keyTable[index] != key) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (long key) {
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + 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 long findKey (Object value, boolean identity, long notFound) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
if (hasZeroValue && zeroValue == null) return 0;
|
||||
long[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != EMPTY && valueTable[i] == null) return keyTable[i];
|
||||
} else if (identity) {
|
||||
if (value == zeroValue) return 0;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return keyTable[i];
|
||||
} else {
|
||||
if (hasZeroValue && value.equals(zeroValue)) return 0;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return keyTable[i];
|
||||
}
|
||||
return notFound;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 63 - Long.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
long[] oldKeyTable = keyTable;
|
||||
V[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = new long[newSize + stashCapacity];
|
||||
valueTable = (V[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = hasZeroValue ? 1 : 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
long key = oldKeyTable[i];
|
||||
if (key != EMPTY) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (long h) {
|
||||
h *= PRIME2;
|
||||
return (int)((h ^ h >>> hashShift) & mask);
|
||||
}
|
||||
|
||||
private int hash3 (long h) {
|
||||
h *= PRIME3;
|
||||
return (int)((h ^ h >>> hashShift) & mask);
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
if (hasZeroValue && zeroValue != null) {
|
||||
h += zeroValue.hashCode();
|
||||
}
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
long key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
h += (int)(key ^ (key >>> 32)) * 31;
|
||||
|
||||
V value = valueTable[i];
|
||||
if (value != null) {
|
||||
h += value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof LongMap)) return false;
|
||||
LongMap<V> other = (LongMap)obj;
|
||||
if (other.size != size) return false;
|
||||
if (other.hasZeroValue != hasZeroValue) return false;
|
||||
if (hasZeroValue) {
|
||||
if (other.zeroValue == null) {
|
||||
if (zeroValue != null) return false;
|
||||
} else {
|
||||
if (!other.zeroValue.equals(zeroValue)) return false;
|
||||
}
|
||||
}
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
long key = keyTable[i];
|
||||
if (key != EMPTY) {
|
||||
V value = valueTable[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) return false;
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "[]";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('[');
|
||||
long[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
long key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
long key = keyTable[i];
|
||||
if (key == EMPTY) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
buffer.append(']');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public Iterator<Entry<V>> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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 (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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 (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<V> {
|
||||
public long key;
|
||||
public V value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator<V> {
|
||||
static final int INDEX_ILLEGAL = -2;
|
||||
static final int INDEX_ZERO = -1;
|
||||
|
||||
public boolean hasNext;
|
||||
|
||||
final LongMap<V> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (LongMap<V> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
nextIndex = INDEX_ZERO;
|
||||
if (map.hasZeroValue)
|
||||
hasNext = true;
|
||||
else
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
long[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != EMPTY) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex == INDEX_ZERO && map.hasZeroValue) {
|
||||
map.zeroValue = null;
|
||||
map.hasZeroValue = false;
|
||||
} else if (currentIndex < 0) {
|
||||
throw new IllegalStateException("next must be called before remove.");
|
||||
} else if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = EMPTY;
|
||||
map.valueTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = INDEX_ILLEGAL;
|
||||
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 (LongMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry<V> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
long[] keyTable = map.keyTable;
|
||||
if (nextIndex == INDEX_ZERO) {
|
||||
entry.key = 0;
|
||||
entry.value = map.zeroValue;
|
||||
} else {
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
}
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Iterator<Entry<V>> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values<V> extends MapIterator<V> implements Iterable<V>, Iterator<V> {
|
||||
public Values (LongMap<V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value;
|
||||
if (nextIndex == INDEX_ZERO)
|
||||
value = map.zeroValue;
|
||||
else
|
||||
value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys extends MapIterator {
|
||||
public Keys (LongMap map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
public long next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
long key = nextIndex == INDEX_ZERO ? 0 : map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public LongArray toArray () {
|
||||
LongArray array = new LongArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
781
src/dorkbox/util/collections/ObjectFloatMap.java
Normal file
781
src/dorkbox/util/collections/ObjectFloatMap.java
Normal file
|
@ -0,0 +1,781 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
|
||||
/** An unordered map where the values are floats. 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 ObjectFloatMap<K> implements Iterable<ObjectFloatMap.Entry<K>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
K[] keyTable;
|
||||
float[] valueTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
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 51 and a load factor of 0.8. */
|
||||
public ObjectFloatMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectFloatMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectFloatMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = new float[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public ObjectFloatMap (ObjectFloatMap<? extends K> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
public void put (K key, float 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 & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key.equals(key1)) {
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key.equals(key2)) {
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key.equals(key3)) {
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (ObjectFloatMap<? extends K> map) {
|
||||
for (Entry<? extends K> entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (K key, float value) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (K insertKey, float insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
K evictedKey;
|
||||
float evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(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 (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (K key, float value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** @param defaultValue Returned if the key was not associated with a value. */
|
||||
public float get (K key, float defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private float getStash (K key, float defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return 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 float getAndIncrement (K key, float defaultValue, float increment) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
float value = valueTable[index];
|
||||
valueTable[index] = value + increment;
|
||||
return value;
|
||||
}
|
||||
|
||||
private float getAndIncrementStash (K key, float defaultValue, float increment) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) {
|
||||
float value = valueTable[i];
|
||||
valueTable[i] = value + increment;
|
||||
return value;
|
||||
}
|
||||
put(key, defaultValue + increment);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public float remove (K key, float defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
float oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key, defaultValue);
|
||||
}
|
||||
|
||||
float removeStash (K key, float defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
float oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.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 (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = null;
|
||||
size = 0;
|
||||
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 (float value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == value) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + 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 (float value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == value) return keyTable[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = keyTable;
|
||||
float[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = new float[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
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 >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
float value = valueTable[i];
|
||||
h += Float.floatToIntBits(value);
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ObjectFloatMap)) return false;
|
||||
ObjectFloatMap<K> other = (ObjectFloatMap) obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
float otherValue = other.get(key, 0f);
|
||||
if (otherValue == 0f && !other.containsKey(key)) return false;
|
||||
float value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
K[] keyTable = this.keyTable;
|
||||
float[] 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();
|
||||
}
|
||||
|
||||
public Entries<K> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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<K> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K> {
|
||||
public K key;
|
||||
public float value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator<K> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectFloatMap<K> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (ObjectFloatMap<K> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries<K> extends MapIterator<K> implements Iterable<Entry<K>>, Iterator<Entry<K>> {
|
||||
private Entry<K> entry = new Entry();
|
||||
|
||||
public Entries (ObjectFloatMap<K> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry<K> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Entries<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator<Object> {
|
||||
public Values (ObjectFloatMap<?> map) {
|
||||
super((ObjectFloatMap<Object>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public float next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
float value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public FloatArray toArray () {
|
||||
FloatArray array = new FloatArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys<K> extends MapIterator<K> implements Iterable<K>, Iterator<K> {
|
||||
public Keys (ObjectFloatMap<K> map) {
|
||||
super((ObjectFloatMap<K>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
public Keys<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Adds the remaining keys to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
/*
|
||||
* Copyright 2010 Mario Zechner (contact@badlogicgames.com), Nathan Sweet (admin@esotericsoftware.com)
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -12,13 +14,17 @@
|
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import com.esotericsoftware.kryo.util.ObjectMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
|
||||
/** 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>
|
||||
|
@ -26,8 +32,7 @@ import dorkbox.util.RandomUtil;
|
|||
* 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")
|
||||
public class ObjectIntMap<K> implements Iterable<ObjectIntMap.Entry<K>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
@ -43,101 +48,83 @@ public class ObjectIntMap<K> {
|
|||
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. */
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
/** Creates a new map with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public ObjectIntMap () {
|
||||
this(32, 0.8f);
|
||||
this(51, 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. */
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
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")
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
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 (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) {
|
||||
throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
}
|
||||
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);
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
this.keyTable = (K[])new Object[this.capacity + this.stashCapacity];
|
||||
this.valueTable = new int[this.keyTable.length];
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = new int[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 putAll(final ObjectIntMap<? extends K> map) {
|
||||
K[] keyTable = map.keyTable;
|
||||
int[] valueTable = map.valueTable;
|
||||
|
||||
for (int i = 0, length = map.capacity + map.stashSize; i < length; i++) {
|
||||
K k = keyTable[i];
|
||||
int v = valueTable[i];
|
||||
if (k != null && v != 0) {
|
||||
put(k, v);
|
||||
}
|
||||
}
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
public void put (K key, int value) {
|
||||
if (key == null) {
|
||||
throw new IllegalArgumentException("key cannot be null.");
|
||||
}
|
||||
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;
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key.equals(key1)) {
|
||||
this.valueTable[index1] = value;
|
||||
valueTable[index1] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key.equals(key2)) {
|
||||
this.valueTable[index2] = value;
|
||||
valueTable[index2] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key.equals(key3)) {
|
||||
this.valueTable[index3] = value;
|
||||
valueTable[index3] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
this.valueTable[i] = value;
|
||||
valueTable[i] = value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -145,68 +132,61 @@ public class ObjectIntMap<K> {
|
|||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
this.valueTable[index1] = value;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
this.valueTable[index2] = value;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
this.valueTable[index3] = value;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
public void putAll (ObjectIntMap<? extends K> map) {
|
||||
for (Entry<? extends K> entry : map.entries())
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** 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];
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
this.keyTable[index1] = key;
|
||||
this.valueTable[index1] = value;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = this.keyTable[index2];
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
this.keyTable[index2] = key;
|
||||
this.valueTable[index2] = value;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = this.keyTable[index3];
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
this.keyTable[index3] = key;
|
||||
this.valueTable[index3] = value;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -225,24 +205,24 @@ public class ObjectIntMap<K> {
|
|||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(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;
|
||||
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.
|
||||
|
@ -252,9 +232,7 @@ public class ObjectIntMap<K> {
|
|||
if (key1 == null) {
|
||||
keyTable[index1] = evictedKey;
|
||||
valueTable[index1] = evictedValue;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -263,9 +241,7 @@ public class ObjectIntMap<K> {
|
|||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -274,15 +250,11 @@ public class ObjectIntMap<K> {
|
|||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (this.size++ >= this.threshold) {
|
||||
resize(this.capacity << 1);
|
||||
}
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) {
|
||||
break;
|
||||
}
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
|
@ -292,101 +264,93 @@ public class ObjectIntMap<K> {
|
|||
}
|
||||
|
||||
private void putStash (K key, int value) {
|
||||
if (this.stashSize == this.stashCapacity) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(this.capacity << 1);
|
||||
put(key, value);
|
||||
resize(capacity << 1);
|
||||
putResize(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++;
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
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])) {
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(this.keyTable[index])) {
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(this.keyTable[index])) {
|
||||
return getStash(key, defaultValue);
|
||||
}
|
||||
if (!key.equals(keyTable[index])) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return this.valueTable[index];
|
||||
return 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];
|
||||
}
|
||||
}
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return 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. */
|
||||
* 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])) {
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(this.keyTable[index])) {
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(this.keyTable[index])) {
|
||||
return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
if (!key.equals(keyTable[index])) return getAndIncrementStash(key, defaultValue, increment);
|
||||
}
|
||||
}
|
||||
int value = this.valueTable[index];
|
||||
this.valueTable[index] = value + increment;
|
||||
int value = valueTable[index];
|
||||
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++) {
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) {
|
||||
int value = this.valueTable[i];
|
||||
this.valueTable[i] = value + increment;
|
||||
int value = valueTable[i];
|
||||
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--;
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(this.keyTable[index])) {
|
||||
this.keyTable[index] = null;
|
||||
int oldValue = this.valueTable[index];
|
||||
this.size--;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(this.keyTable[index])) {
|
||||
this.keyTable[index] = null;
|
||||
int oldValue = this.valueTable[index];
|
||||
this.size--;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
int oldValue = valueTable[index];
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
|
@ -395,11 +359,11 @@ public class ObjectIntMap<K> {
|
|||
|
||||
int removeStash (K key, int defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = this.capacity, n = i + this.stashSize; i < n; i++) {
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
int oldValue = this.valueTable[i];
|
||||
int oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
this.size--;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
@ -408,71 +372,68 @@ public class ObjectIntMap<K> {
|
|||
|
||||
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;
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
this.keyTable[index] = this.keyTable[lastIndex];
|
||||
this.valueTable[index] = this.valueTable[lastIndex];
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
* 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);
|
||||
if (maximumCapacity < 0) throw new IllegalArgumentException("maximumCapacity must be >= 0: " + maximumCapacity);
|
||||
if (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.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) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
this.size = 0;
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = this.capacity + this.stashSize; i-- > 0;) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = null;
|
||||
}
|
||||
this.size = 0;
|
||||
this.stashSize = 0;
|
||||
size = 0;
|
||||
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. */
|
||||
* an expensive operation. */
|
||||
public boolean containsValue (int value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = this.capacity + this.stashSize; i-- > 0;) {
|
||||
if (keyTable[i] != null && valueTable[i] == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && 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])) {
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(this.keyTable[index])) {
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(this.keyTable[index])) {
|
||||
return containsKeyStash(key);
|
||||
}
|
||||
if (!key.equals(keyTable[index])) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -480,80 +441,103 @@ public class ObjectIntMap<K> {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
for (int i = capacity, n = i + 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. */
|
||||
* every value, which may be an expensive operation. */
|
||||
public K findKey (int value) {
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = this.capacity + this.stashSize; i-- > 0;) {
|
||||
if (keyTable[i] != null && valueTable[i] == value) {
|
||||
return this.keyTable[i];
|
||||
}
|
||||
}
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == value) return 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. */
|
||||
/** Increases the size of the backing array to accommodate 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)));
|
||||
}
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = this.capacity + this.stashSize;
|
||||
int oldEndIndex = capacity + 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);
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = this.keyTable;
|
||||
int[] oldValueTable = this.valueTable;
|
||||
K[] oldKeyTable = keyTable;
|
||||
int[] oldValueTable = valueTable;
|
||||
|
||||
this.keyTable = (K[])new Object[newSize + this.stashCapacity];
|
||||
this.valueTable = new int[newSize + this.stashCapacity];
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = new int[newSize + stashCapacity];
|
||||
|
||||
int oldSize = this.size;
|
||||
this.size = 0;
|
||||
this.stashSize = 0;
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
K key = oldKeyTable[i];
|
||||
if (key != null) {
|
||||
putResize(key, oldValueTable[i]);
|
||||
}
|
||||
if (key != null) putResize(key, oldValueTable[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> this.hashShift) & this.mask;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> this.hashShift) & this.mask;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString () {
|
||||
if (this.size == 0) {
|
||||
return "{}";
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
int value = valueTable[i];
|
||||
h += value;
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ObjectIntMap)) return false;
|
||||
ObjectIntMap<K> other = (ObjectIntMap)obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
int[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
int otherValue = other.get(key, 0);
|
||||
if (otherValue == 0 && !other.containsKey(key)) return false;
|
||||
int value = valueTable[i];
|
||||
if (otherValue != value) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
K[] keyTable = this.keyTable;
|
||||
|
@ -561,9 +545,7 @@ public class ObjectIntMap<K> {
|
|||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
if (key == null) continue;
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
|
@ -571,9 +553,7 @@ public class ObjectIntMap<K> {
|
|||
}
|
||||
while (i-- > 0) {
|
||||
K key = keyTable[i];
|
||||
if (key == null) {
|
||||
continue;
|
||||
}
|
||||
if (key == null) continue;
|
||||
buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
|
@ -582,4 +562,221 @@ public class ObjectIntMap<K> {
|
|||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public Entries<K> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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<K> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K> {
|
||||
public K key;
|
||||
public int value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private class MapIterator<K> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectIntMap<K> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (ObjectIntMap<K> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries<K> extends MapIterator<K> implements Iterable<Entry<K>>, Iterator<Entry<K>> {
|
||||
private Entry<K> entry = new Entry();
|
||||
|
||||
public Entries (ObjectIntMap<K> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry<K> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Entries<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values extends MapIterator<Object> {
|
||||
public Values (ObjectIntMap<?> map) {
|
||||
super((ObjectIntMap<Object>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public int next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
int value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public IntArray toArray () {
|
||||
IntArray array = new IntArray(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys<K> extends MapIterator<K> implements Iterable<K>, Iterator<K> {
|
||||
public Keys (ObjectIntMap<K> map) {
|
||||
super((ObjectIntMap<K>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
public Keys<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
Array array = new Array(true, map.size);
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Adds the remaining keys to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
super.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
819
src/dorkbox/util/collections/ObjectMap.java
Normal file
819
src/dorkbox/util/collections/ObjectMap.java
Normal file
|
@ -0,0 +1,819 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** An unordered map. This implementation is a cuckoo hash map using 3 hashes, random walking, and a small stash for problematic
|
||||
* keys. Null keys are not allowed. 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.<br>
|
||||
* <br>
|
||||
* Iteration can be very slow for a map with a large capacity. {@link #clear(int)} and {@link #shrink(int)} can be used to reduce
|
||||
* the capacity. {@link OrderedMap} provides much faster iteration.
|
||||
* @author Nathan Sweet */
|
||||
public class ObjectMap<K, V> implements Iterable<ObjectMap.Entry<K, V>> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
K[] keyTable;
|
||||
V[] valueTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
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 51 and a load factor of 0.8. */
|
||||
public ObjectMap () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectMap (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new map with the specified initial capacity and load factor. This map will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectMap (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (K[])new Object[capacity + stashCapacity];
|
||||
valueTable = (V[])new Object[keyTable.length];
|
||||
}
|
||||
|
||||
/** Creates a new map identical to the specified map. */
|
||||
public ObjectMap (ObjectMap<? extends K, ? extends V> map) {
|
||||
this((int)Math.floor(map.capacity * map.loadFactor), map.loadFactor);
|
||||
stashSize = map.stashSize;
|
||||
System.arraycopy(map.keyTable, 0, keyTable, 0, map.keyTable.length);
|
||||
System.arraycopy(map.valueTable, 0, valueTable, 0, map.valueTable.length);
|
||||
size = map.size;
|
||||
}
|
||||
|
||||
/** Returns the old value associated with the specified key, or null. */
|
||||
public V put (K key, V 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 & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key.equals(key1)) {
|
||||
V oldValue = valueTable[index1];
|
||||
valueTable[index1] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key.equals(key2)) {
|
||||
V oldValue = valueTable[index2];
|
||||
valueTable[index2] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key.equals(key3)) {
|
||||
V oldValue = valueTable[index3];
|
||||
valueTable[index3] = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
// Update key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
V oldValue = valueTable[i];
|
||||
valueTable[i] = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return null;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void putAll (ObjectMap<? extends K, ? extends V> map) {
|
||||
ensureCapacity(map.size);
|
||||
for (Entry<? extends K, ? extends V> entry : map)
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void putResize (K key, V value) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
K key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
valueTable[index1] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
K key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
valueTable[index2] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
K key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
valueTable[index3] = value;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, value, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (K insertKey, V insertValue, int index1, K key1, int index2, K key2, int index3, K key3) {
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
K evictedKey;
|
||||
V evictedValue;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(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 (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
valueTable[index2] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
valueTable[index3] = evictedValue;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
insertValue = evictedValue;
|
||||
} while (true);
|
||||
|
||||
putStash(evictedKey, evictedValue);
|
||||
}
|
||||
|
||||
private void putStash (K key, V value) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
putResize(key, value);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
valueTable[index] = value;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** Returns the value for the specified key, or null if the key is not in the map. */
|
||||
public V get (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getStash(key, null);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
/** Returns the value for the specified key, or the default value if the key is not in the map. */
|
||||
public V get (K key, V defaultValue) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getStash(key, defaultValue);
|
||||
}
|
||||
}
|
||||
return valueTable[index];
|
||||
}
|
||||
|
||||
private V getStash (K key, V defaultValue) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return valueTable[i];
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** Returns the value associated with the key, or null. */
|
||||
public V remove (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
V oldValue = valueTable[index];
|
||||
valueTable[index] = null;
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
V removeStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
V oldValue = valueTable[i];
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
valueTable[index] = valueTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
valueTable[lastIndex] = null;
|
||||
} else {
|
||||
keyTable[index] = null;
|
||||
valueTable[index] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the map is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map and reduces the size of the backing arrays to be the specified capacity, if they are larger. The reduction
|
||||
* is done by allocating new arrays, though for large arrays this can be faster than clearing the existing array. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the map, leaving the backing arrays at the current capacity. When the capacity is high and the population is low,
|
||||
* iteration can be unnecessarily slow. {@link #clear(int)} can be used to reduce the capacity. */
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;) {
|
||||
keyTable[i] = null;
|
||||
valueTable[i] = null;
|
||||
}
|
||||
size = 0;
|
||||
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.
|
||||
* @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) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return true;
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return true;
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean containsKey (K key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return containsKeyStash(key);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsKeyStash (K key) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + 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.
|
||||
* @param identity If true, uses == to compare the specified value with values in the map. If false, uses
|
||||
* {@link #equals(Object)}. */
|
||||
public K findKey (Object value, boolean identity) {
|
||||
V[] valueTable = this.valueTable;
|
||||
if (value == null) {
|
||||
K[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (keyTable[i] != null && valueTable[i] == null) return keyTable[i];
|
||||
} else if (identity) {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (valueTable[i] == value) return keyTable[i];
|
||||
} else {
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
if (value.equals(valueTable[i])) return keyTable[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
K[] oldKeyTable = keyTable;
|
||||
V[] oldValueTable = valueTable;
|
||||
|
||||
keyTable = (K[])new Object[newSize + stashCapacity];
|
||||
valueTable = (V[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
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 >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
h += key.hashCode() * 31;
|
||||
|
||||
V value = valueTable[i];
|
||||
if (value != null) {
|
||||
h += value.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (!(obj instanceof ObjectMap)) return false;
|
||||
ObjectMap<K, V> other = (ObjectMap)obj;
|
||||
if (other.size != size) return false;
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] valueTable = this.valueTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++) {
|
||||
K key = keyTable[i];
|
||||
if (key != null) {
|
||||
V value = valueTable[i];
|
||||
if (value == null) {
|
||||
if (!other.containsKey(key) || other.get(key) != null) return false;
|
||||
} else {
|
||||
if (!value.equals(other.get(key))) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
return toString(separator, false);
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
return toString(", ", true);
|
||||
}
|
||||
|
||||
private String toString (String separator, boolean braces) {
|
||||
if (size == 0) return braces ? "{}" : "";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
if (braces) buffer.append('{');
|
||||
K[] keyTable = this.keyTable;
|
||||
V[] 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(separator);
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(valueTable[i]);
|
||||
}
|
||||
if (braces) buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public Entries<K, V> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new Entries(this);
|
||||
entries2 = new Entries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 Values} constructor for nested or multithreaded iteration. */
|
||||
public Values<V> values () {
|
||||
if (values1 == null) {
|
||||
values1 = new Values(this);
|
||||
values2 = new Values(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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 Keys} constructor for nested or multithreaded iteration. */
|
||||
public Keys<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new Keys(this);
|
||||
keys2 = new Keys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
static public class Entry<K, V> {
|
||||
public K key;
|
||||
public V value;
|
||||
|
||||
public String toString () {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
static private abstract class MapIterator<K, V, I> implements Iterable<I>, Iterator<I> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectMap<K, V> map;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public MapIterator (ObjectMap<K, V> map) {
|
||||
this.map = map;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = map.keyTable;
|
||||
for (int n = map.capacity + map.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= map.capacity) {
|
||||
map.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
map.keyTable[currentIndex] = null;
|
||||
map.valueTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
map.size--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Entries<K, V> extends MapIterator<K, V, Entry<K, V>> {
|
||||
Entry<K, V> entry = new Entry();
|
||||
|
||||
public Entries (ObjectMap<K, V> map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/** Note the same entry instance is returned each time this method is called. */
|
||||
public Entry<K, V> next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K[] keyTable = map.keyTable;
|
||||
entry.key = keyTable[nextIndex];
|
||||
entry.value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public Entries<K, V> iterator () {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Values<V> extends MapIterator<Object, V, V> {
|
||||
public Values (ObjectMap<?, V> map) {
|
||||
super((ObjectMap<Object, V>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value = map.valueTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return value;
|
||||
}
|
||||
|
||||
public Values<V> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public Array<V> toArray () {
|
||||
return toArray(new Array(true, map.size));
|
||||
}
|
||||
|
||||
/** Adds the remaining values to the specified array. */
|
||||
public Array<V> toArray (Array<V> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static public class Keys<K> extends MapIterator<K, Object, K> {
|
||||
public Keys (ObjectMap<K, ?> map) {
|
||||
super((ObjectMap<K, Object>)map);
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = map.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
public Keys<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining keys. */
|
||||
public Array<K> toArray () {
|
||||
return toArray(new Array(true, map.size));
|
||||
}
|
||||
|
||||
/** Adds the remaining keys to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
581
src/dorkbox/util/collections/ObjectSet.java
Normal file
581
src/dorkbox/util/collections/ObjectSet.java
Normal file
|
@ -0,0 +1,581 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dorkbox.util.MathUtil;
|
||||
import dorkbox.util.RandomUtil;
|
||||
|
||||
/** An unordered set where the keys are objects. This implementation uses cuckoo hashing 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 set performs very fast contains and remove (typically O(1), worst case O(log(n))). Add may be a bit slower, depending on
|
||||
* hash collisions. Load factors greater than 0.91 greatly increase the chances the set will have to rehash to the next higher POT
|
||||
* size.<br>
|
||||
* <br>
|
||||
* Iteration can be very slow for a set with a large capacity. {@link #clear(int)} and {@link #shrink(int)} can be used to reduce
|
||||
* the capacity. {@link OrderedSet} provides much faster iteration.
|
||||
* @author Nathan Sweet */
|
||||
public class ObjectSet<T> implements Iterable<T> {
|
||||
private static final int PRIME1 = 0xbe1f14b1;
|
||||
private static final int PRIME2 = 0xb4b82e39;
|
||||
private static final int PRIME3 = 0xced1c241;
|
||||
|
||||
public int size;
|
||||
|
||||
T[] keyTable;
|
||||
int capacity, stashSize;
|
||||
|
||||
private float loadFactor;
|
||||
private int hashShift, mask, threshold;
|
||||
private int stashCapacity;
|
||||
private int pushIterations;
|
||||
|
||||
private ObjectSetIterator iterator1, iterator2;
|
||||
|
||||
/** Creates a new set with an initial capacity of 51 and a load factor of 0.8. */
|
||||
public ObjectSet () {
|
||||
this(51, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with a load factor of 0.8.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectSet (int initialCapacity) {
|
||||
this(initialCapacity, 0.8f);
|
||||
}
|
||||
|
||||
/** Creates a new set with the specified initial capacity and load factor. This set will hold initialCapacity items before
|
||||
* growing the backing table.
|
||||
* @param initialCapacity If not a power of two, it is increased to the next nearest power of two. */
|
||||
public ObjectSet (int initialCapacity, float loadFactor) {
|
||||
if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity must be >= 0: " + initialCapacity);
|
||||
initialCapacity = MathUtil.nextPowerOfTwo((int)Math.ceil(initialCapacity / loadFactor));
|
||||
if (initialCapacity > 1 << 30) throw new IllegalArgumentException("initialCapacity is too large: " + initialCapacity);
|
||||
capacity = initialCapacity;
|
||||
|
||||
if (loadFactor <= 0) throw new IllegalArgumentException("loadFactor must be > 0: " + loadFactor);
|
||||
this.loadFactor = loadFactor;
|
||||
|
||||
threshold = (int)(capacity * loadFactor);
|
||||
mask = capacity - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(capacity);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(capacity)) * 2);
|
||||
pushIterations = Math.max(Math.min(capacity, 8), (int)Math.sqrt(capacity) / 8);
|
||||
|
||||
keyTable = (T[])new Object[capacity + stashCapacity];
|
||||
}
|
||||
|
||||
/** Creates a new set identical to the specified set. */
|
||||
public ObjectSet (ObjectSet set) {
|
||||
this((int)Math.floor(set.capacity * set.loadFactor), set.loadFactor);
|
||||
stashSize = set.stashSize;
|
||||
System.arraycopy(set.keyTable, 0, keyTable, 0, set.keyTable.length);
|
||||
size = set.size;
|
||||
}
|
||||
|
||||
/** Returns true if the key was not already in the set. If this set already contains the key, the call leaves the set unchanged
|
||||
* and returns false. */
|
||||
public boolean add (T key) {
|
||||
if (key == null) throw new IllegalArgumentException("key cannot be null.");
|
||||
T[] keyTable = this.keyTable;
|
||||
|
||||
// Check for existing keys.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
T key1 = keyTable[index1];
|
||||
if (key.equals(key1)) return false;
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
T key2 = keyTable[index2];
|
||||
if (key.equals(key2)) return false;
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
T key3 = keyTable[index3];
|
||||
if (key.equals(key3)) return false;
|
||||
|
||||
// Find key in the stash.
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return false;
|
||||
|
||||
// Check for empty buckets.
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array) {
|
||||
addAll(array.items, 0, array.size);
|
||||
}
|
||||
|
||||
public void addAll (Array<? extends T> array, int offset, int length) {
|
||||
if (offset + length > array.size)
|
||||
throw new IllegalArgumentException("offset + length must be <= size: " + offset + " + " + length + " <= " + array.size);
|
||||
addAll((T[])array.items, offset, length);
|
||||
}
|
||||
|
||||
public void addAll (T... array) {
|
||||
addAll(array, 0, array.length);
|
||||
}
|
||||
|
||||
public void addAll (T[] array, int offset, int length) {
|
||||
ensureCapacity(length);
|
||||
for (int i = offset, n = i + length; i < n; i++)
|
||||
add(array[i]);
|
||||
}
|
||||
|
||||
public void addAll (ObjectSet<T> set) {
|
||||
ensureCapacity(set.size);
|
||||
for (T key : set)
|
||||
add(key);
|
||||
}
|
||||
|
||||
/** Skips checks for existing keys. */
|
||||
private void addResize (T key) {
|
||||
// Check for empty buckets.
|
||||
int hashCode = key.hashCode();
|
||||
int index1 = hashCode & mask;
|
||||
T key1 = keyTable[index1];
|
||||
if (key1 == null) {
|
||||
keyTable[index1] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index2 = hash2(hashCode);
|
||||
T key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int index3 = hash3(hashCode);
|
||||
T key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = key;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
push(key, index1, key1, index2, key2, index3, key3);
|
||||
}
|
||||
|
||||
private void push (T insertKey, int index1, T key1, int index2, T key2, int index3, T key3) {
|
||||
T[] keyTable = this.keyTable;
|
||||
int mask = this.mask;
|
||||
|
||||
// Push keys until an empty bucket is found.
|
||||
T evictedKey;
|
||||
int i = 0, pushIterations = this.pushIterations;
|
||||
do {
|
||||
// Replace the key and value for one of the hashes.
|
||||
switch (RandomUtil.int_(2)) {
|
||||
case 0:
|
||||
evictedKey = key1;
|
||||
keyTable[index1] = insertKey;
|
||||
break;
|
||||
case 1:
|
||||
evictedKey = key2;
|
||||
keyTable[index2] = insertKey;
|
||||
break;
|
||||
default:
|
||||
evictedKey = key3;
|
||||
keyTable[index3] = insertKey;
|
||||
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;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index2 = hash2(hashCode);
|
||||
key2 = keyTable[index2];
|
||||
if (key2 == null) {
|
||||
keyTable[index2] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
index3 = hash3(hashCode);
|
||||
key3 = keyTable[index3];
|
||||
if (key3 == null) {
|
||||
keyTable[index3] = evictedKey;
|
||||
if (size++ >= threshold) resize(capacity << 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (++i == pushIterations) break;
|
||||
|
||||
insertKey = evictedKey;
|
||||
} while (true);
|
||||
|
||||
addStash(evictedKey);
|
||||
}
|
||||
|
||||
private void addStash (T key) {
|
||||
if (stashSize == stashCapacity) {
|
||||
// Too many pushes occurred and the stash is full, increase the table size.
|
||||
resize(capacity << 1);
|
||||
addResize(key);
|
||||
return;
|
||||
}
|
||||
// Store key in the stash.
|
||||
int index = capacity + stashSize;
|
||||
keyTable[index] = key;
|
||||
stashSize++;
|
||||
size++;
|
||||
}
|
||||
|
||||
/** Returns true if the key was removed. */
|
||||
public boolean remove (T key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash2(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
index = hash3(hashCode);
|
||||
if (key.equals(keyTable[index])) {
|
||||
keyTable[index] = null;
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
return removeStash(key);
|
||||
}
|
||||
|
||||
boolean removeStash (T key) {
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++) {
|
||||
if (key.equals(keyTable[i])) {
|
||||
removeStashIndex(i);
|
||||
size--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void removeStashIndex (int index) {
|
||||
// If the removed location was not last, move the last tuple to the removed location.
|
||||
stashSize--;
|
||||
int lastIndex = capacity + stashSize;
|
||||
if (index < lastIndex) {
|
||||
keyTable[index] = keyTable[lastIndex];
|
||||
keyTable[lastIndex] = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the set is empty. */
|
||||
public boolean isEmpty () {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/** 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 set 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 (size > maximumCapacity) maximumCapacity = size;
|
||||
if (capacity <= maximumCapacity) return;
|
||||
maximumCapacity = MathUtil.nextPowerOfTwo(maximumCapacity);
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the set and reduces the size of the backing arrays to be the specified capacity, if they are larger. The reduction
|
||||
* is done by allocating new arrays, though for large arrays this can be faster than clearing the existing array. */
|
||||
public void clear (int maximumCapacity) {
|
||||
if (capacity <= maximumCapacity) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
size = 0;
|
||||
resize(maximumCapacity);
|
||||
}
|
||||
|
||||
/** Clears the set, leaving the backing arrays at the current capacity. When the capacity is high and the population is low,
|
||||
* iteration can be unnecessarily slow. {@link #clear(int)} can be used to reduce the capacity. */
|
||||
public void clear () {
|
||||
if (size == 0) return;
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = capacity + stashSize; i-- > 0;)
|
||||
keyTable[i] = null;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
}
|
||||
|
||||
public boolean contains (T key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash2(hashCode);
|
||||
if (!key.equals(keyTable[index])) {
|
||||
index = hash3(hashCode);
|
||||
if (!key.equals(keyTable[index])) return getKeyStash(key) != null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return May be null. */
|
||||
public T get (T key) {
|
||||
int hashCode = key.hashCode();
|
||||
int index = hashCode & mask;
|
||||
T found = keyTable[index];
|
||||
if (!key.equals(found)) {
|
||||
index = hash2(hashCode);
|
||||
found = keyTable[index];
|
||||
if (!key.equals(found)) {
|
||||
index = hash3(hashCode);
|
||||
found = keyTable[index];
|
||||
if (!key.equals(found)) return getKeyStash(key);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
private T getKeyStash (T key) {
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = capacity, n = i + stashSize; i < n; i++)
|
||||
if (key.equals(keyTable[i])) return keyTable[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
public T first () {
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != null) return keyTable[i];
|
||||
throw new IllegalStateException("ObjectSet is empty.");
|
||||
}
|
||||
|
||||
/** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many
|
||||
* items to avoid multiple backing array resizes. */
|
||||
public void ensureCapacity (int additionalCapacity) {
|
||||
if (additionalCapacity < 0) throw new IllegalArgumentException("additionalCapacity must be >= 0: " + additionalCapacity);
|
||||
int sizeNeeded = size + additionalCapacity;
|
||||
if (sizeNeeded >= threshold) resize(MathUtil.nextPowerOfTwo((int)Math.ceil(sizeNeeded / loadFactor)));
|
||||
}
|
||||
|
||||
private void resize (int newSize) {
|
||||
int oldEndIndex = capacity + stashSize;
|
||||
|
||||
capacity = newSize;
|
||||
threshold = (int)(newSize * loadFactor);
|
||||
mask = newSize - 1;
|
||||
hashShift = 31 - Integer.numberOfTrailingZeros(newSize);
|
||||
stashCapacity = Math.max(3, (int)Math.ceil(Math.log(newSize)) * 2);
|
||||
pushIterations = Math.max(Math.min(newSize, 8), (int)Math.sqrt(newSize) / 8);
|
||||
|
||||
T[] oldKeyTable = keyTable;
|
||||
|
||||
keyTable = (T[])new Object[newSize + stashCapacity];
|
||||
|
||||
int oldSize = size;
|
||||
size = 0;
|
||||
stashSize = 0;
|
||||
if (oldSize > 0) {
|
||||
for (int i = 0; i < oldEndIndex; i++) {
|
||||
T key = oldKeyTable[i];
|
||||
if (key != null) addResize(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int hash2 (int h) {
|
||||
h *= PRIME2;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
private int hash3 (int h) {
|
||||
h *= PRIME3;
|
||||
return (h ^ h >>> hashShift) & mask;
|
||||
}
|
||||
|
||||
public int hashCode () {
|
||||
int h = 0;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != null) h += keyTable[i].hashCode();
|
||||
return h;
|
||||
}
|
||||
|
||||
public boolean equals (Object obj) {
|
||||
if (!(obj instanceof ObjectSet)) return false;
|
||||
ObjectSet other = (ObjectSet)obj;
|
||||
if (other.size != size) return false;
|
||||
T[] keyTable = this.keyTable;
|
||||
for (int i = 0, n = capacity + stashSize; i < n; i++)
|
||||
if (keyTable[i] != null && !other.contains(keyTable[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
return '{' + toString(", ") + '}';
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
if (size == 0) return "";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
T[] keyTable = this.keyTable;
|
||||
int i = keyTable.length;
|
||||
while (i-- > 0) {
|
||||
T key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(key);
|
||||
break;
|
||||
}
|
||||
while (i-- > 0) {
|
||||
T key = keyTable[i];
|
||||
if (key == null) continue;
|
||||
buffer.append(separator);
|
||||
buffer.append(key);
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/** Returns an iterator for the keys in the set. Remove is supported. Note that the same iterator instance is returned each
|
||||
* time this method is called. Use the {@link ObjectSetIterator} constructor for nested or multithreaded iteration. */
|
||||
public ObjectSetIterator<T> iterator () {
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new ObjectSetIterator(this);
|
||||
iterator2 = new ObjectSetIterator(this);
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.reset();
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.reset();
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
|
||||
static public <T> ObjectSet<T> with (T... array) {
|
||||
ObjectSet set = new ObjectSet();
|
||||
set.addAll(array);
|
||||
return set;
|
||||
}
|
||||
|
||||
static public class ObjectSetIterator<K> implements Iterable<K>, Iterator<K> {
|
||||
public boolean hasNext;
|
||||
|
||||
final ObjectSet<K> set;
|
||||
int nextIndex, currentIndex;
|
||||
boolean valid = true;
|
||||
|
||||
public ObjectSetIterator (ObjectSet<K> set) {
|
||||
this.set = set;
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
currentIndex = -1;
|
||||
nextIndex = -1;
|
||||
findNextIndex();
|
||||
}
|
||||
|
||||
private void findNextIndex () {
|
||||
hasNext = false;
|
||||
K[] keyTable = set.keyTable;
|
||||
for (int n = set.capacity + set.stashSize; ++nextIndex < n;) {
|
||||
if (keyTable[nextIndex] != null) {
|
||||
hasNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
if (currentIndex >= set.capacity) {
|
||||
set.removeStashIndex(currentIndex);
|
||||
nextIndex = currentIndex - 1;
|
||||
findNextIndex();
|
||||
} else {
|
||||
set.keyTable[currentIndex] = null;
|
||||
}
|
||||
currentIndex = -1;
|
||||
set.size--;
|
||||
}
|
||||
|
||||
public boolean hasNext () {
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = set.keyTable[nextIndex];
|
||||
currentIndex = nextIndex;
|
||||
findNextIndex();
|
||||
return key;
|
||||
}
|
||||
|
||||
public ObjectSetIterator<K> iterator () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Adds the remaining values to the array. */
|
||||
public Array<K> toArray (Array<K> array) {
|
||||
while (hasNext)
|
||||
array.add(next());
|
||||
return array;
|
||||
}
|
||||
|
||||
/** Returns a new array containing the remaining values. */
|
||||
public Array<K> toArray () {
|
||||
return toArray(new Array(true, set.size));
|
||||
}
|
||||
}
|
||||
}
|
251
src/dorkbox/util/collections/OrderedMap.java
Normal file
251
src/dorkbox/util/collections/OrderedMap.java
Normal file
|
@ -0,0 +1,251 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An {@link ObjectMap} that also stores keys in an {@link Array} using the insertion order. Iteration over the
|
||||
* {@link #entries()}, {@link #keys()}, and {@link #values()} is ordered and faster than an unordered map. Keys can also be
|
||||
* accessed and the order changed using {@link #orderedKeys()}. There is some additional overhead for put and remove. When used
|
||||
* for faster iteration versus ObjectMap and the order does not actually matter, copying during remove can be greatly reduced by
|
||||
* setting {@link Array#ordered} to false for {@link OrderedMap#orderedKeys()}.
|
||||
* @author Nathan Sweet */
|
||||
public class OrderedMap<K, V> extends ObjectMap<K, V> {
|
||||
final Array<K> keys;
|
||||
|
||||
private Entries entries1, entries2;
|
||||
private Values values1, values2;
|
||||
private Keys keys1, keys2;
|
||||
|
||||
public OrderedMap () {
|
||||
keys = new Array();
|
||||
}
|
||||
|
||||
public OrderedMap (int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
keys = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedMap (int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
keys = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedMap (OrderedMap<? extends K, ? extends V> map) {
|
||||
super(map);
|
||||
keys = new Array(map.keys);
|
||||
}
|
||||
|
||||
public V put (K key, V value) {
|
||||
if (!containsKey(key)) keys.add(key);
|
||||
return super.put(key, value);
|
||||
}
|
||||
|
||||
public V remove (K key) {
|
||||
keys.removeValue(key, false);
|
||||
return super.remove(key);
|
||||
}
|
||||
|
||||
public V removeIndex (int index) {
|
||||
return super.remove(keys.removeIndex(index));
|
||||
}
|
||||
|
||||
public void clear (int maximumCapacity) {
|
||||
keys.clear();
|
||||
super.clear(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
keys.clear();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
public Array<K> orderedKeys () {
|
||||
return keys;
|
||||
}
|
||||
|
||||
public Entries<K, V> iterator () {
|
||||
return entries();
|
||||
}
|
||||
|
||||
/** 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 OrderedMapEntries} constructor for nested or multithreaded iteration. */
|
||||
public Entries<K, V> entries () {
|
||||
if (entries1 == null) {
|
||||
entries1 = new OrderedMapEntries(this);
|
||||
entries2 = new OrderedMapEntries(this);
|
||||
}
|
||||
if (!entries1.valid) {
|
||||
entries1.reset();
|
||||
entries1.valid = true;
|
||||
entries2.valid = false;
|
||||
return entries1;
|
||||
}
|
||||
entries2.reset();
|
||||
entries2.valid = true;
|
||||
entries1.valid = false;
|
||||
return 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 OrderedMapValues} constructor for nested or multithreaded iteration. */
|
||||
public Values<V> values () {
|
||||
if (values1 == null) {
|
||||
values1 = new OrderedMapValues(this);
|
||||
values2 = new OrderedMapValues(this);
|
||||
}
|
||||
if (!values1.valid) {
|
||||
values1.reset();
|
||||
values1.valid = true;
|
||||
values2.valid = false;
|
||||
return values1;
|
||||
}
|
||||
values2.reset();
|
||||
values2.valid = true;
|
||||
values1.valid = false;
|
||||
return 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 OrderedMapKeys} constructor for nested or multithreaded iteration. */
|
||||
public Keys<K> keys () {
|
||||
if (keys1 == null) {
|
||||
keys1 = new OrderedMapKeys(this);
|
||||
keys2 = new OrderedMapKeys(this);
|
||||
}
|
||||
if (!keys1.valid) {
|
||||
keys1.reset();
|
||||
keys1.valid = true;
|
||||
keys2.valid = false;
|
||||
return keys1;
|
||||
}
|
||||
keys2.reset();
|
||||
keys2.valid = true;
|
||||
keys1.valid = false;
|
||||
return keys2;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
Array<K> keys = this.keys;
|
||||
for (int i = 0, n = keys.size; i < n; i++) {
|
||||
K key = keys.get(i);
|
||||
if (i > 0) buffer.append(", ");
|
||||
buffer.append(key);
|
||||
buffer.append('=');
|
||||
buffer.append(get(key));
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
static public class OrderedMapEntries<K, V> extends Entries<K, V> {
|
||||
private Array<K> keys;
|
||||
|
||||
public OrderedMapEntries (OrderedMap<K, V> map) {
|
||||
super(map);
|
||||
keys = map.keys;
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = map.size > 0;
|
||||
}
|
||||
|
||||
public Entry next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
entry.key = keys.get(nextIndex);
|
||||
entry.value = map.get(entry.key);
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < map.size;
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
map.remove(entry.key);
|
||||
nextIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
static public class OrderedMapKeys<K> extends Keys<K> {
|
||||
private Array<K> keys;
|
||||
|
||||
public OrderedMapKeys (OrderedMap<K, ?> map) {
|
||||
super(map);
|
||||
keys = map.keys;
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = map.size > 0;
|
||||
}
|
||||
|
||||
public K next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
K key = keys.get(nextIndex);
|
||||
currentIndex = nextIndex;
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < map.size;
|
||||
return key;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
((OrderedMap)map).removeIndex(nextIndex - 1);
|
||||
nextIndex = currentIndex;
|
||||
currentIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static public class OrderedMapValues<V> extends Values<V> {
|
||||
private Array keys;
|
||||
|
||||
public OrderedMapValues (OrderedMap<?, V> map) {
|
||||
super(map);
|
||||
keys = map.keys;
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = map.size > 0;
|
||||
}
|
||||
|
||||
public V next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
V value = (V)map.get(keys.get(nextIndex));
|
||||
currentIndex = nextIndex;
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < map.size;
|
||||
return value;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (currentIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
((OrderedMap)map).removeIndex(currentIndex);
|
||||
nextIndex = currentIndex;
|
||||
currentIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
158
src/dorkbox/util/collections/OrderedSet.java
Normal file
158
src/dorkbox/util/collections/OrderedSet.java
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/** An {@link ObjectSet} that also stores keys in an {@link Array} using the insertion order. {@link #iterator() Iteration} is
|
||||
* ordered and faster than an unordered set. Keys can also be accessed and the order changed using {@link #orderedItems()}. There
|
||||
* is some additional overhead for put and remove. When used for faster iteration versus ObjectSet and the order does not actually
|
||||
* matter, copying during remove can be greatly reduced by setting {@link Array#ordered} to false for
|
||||
* {@link OrderedSet#orderedItems()}.
|
||||
* @author Nathan Sweet */
|
||||
public class OrderedSet<T> extends ObjectSet<T> {
|
||||
final Array<T> items;
|
||||
OrderedSetIterator iterator1, iterator2;
|
||||
|
||||
public OrderedSet () {
|
||||
items = new Array();
|
||||
}
|
||||
|
||||
public OrderedSet (int initialCapacity, float loadFactor) {
|
||||
super(initialCapacity, loadFactor);
|
||||
items = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedSet (int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
items = new Array(capacity);
|
||||
}
|
||||
|
||||
public OrderedSet (OrderedSet set) {
|
||||
super(set);
|
||||
items = new Array(capacity);
|
||||
items.addAll(set.items);
|
||||
}
|
||||
|
||||
public boolean add (T key) {
|
||||
if (!super.add(key)) return false;
|
||||
items.add(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean add (T key, int index) {
|
||||
if (!super.add(key)) {
|
||||
items.removeValue(key, true);
|
||||
items.insert(index, key);
|
||||
return false;
|
||||
}
|
||||
items.insert(index, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean remove (T key) {
|
||||
if (!super.remove(key)) return false;
|
||||
items.removeValue(key, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public T removeIndex (int index) {
|
||||
T key = items.removeIndex(index);
|
||||
super.remove(key);
|
||||
return key;
|
||||
}
|
||||
|
||||
public void clear (int maximumCapacity) {
|
||||
items.clear();
|
||||
super.clear(maximumCapacity);
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
items.clear();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
public Array<T> orderedItems () {
|
||||
return items;
|
||||
}
|
||||
|
||||
public OrderedSetIterator<T> iterator () {
|
||||
if (iterator1 == null) {
|
||||
iterator1 = new OrderedSetIterator(this);
|
||||
iterator2 = new OrderedSetIterator(this);
|
||||
}
|
||||
if (!iterator1.valid) {
|
||||
iterator1.reset();
|
||||
iterator1.valid = true;
|
||||
iterator2.valid = false;
|
||||
return iterator1;
|
||||
}
|
||||
iterator2.reset();
|
||||
iterator2.valid = true;
|
||||
iterator1.valid = false;
|
||||
return iterator2;
|
||||
}
|
||||
|
||||
public String toString () {
|
||||
if (size == 0) return "{}";
|
||||
T[] items = this.items.items;
|
||||
StringBuilder buffer = new StringBuilder(32);
|
||||
buffer.append('{');
|
||||
buffer.append(items[0]);
|
||||
for (int i = 1; i < size; i++) {
|
||||
buffer.append(", ");
|
||||
buffer.append(items[i]);
|
||||
}
|
||||
buffer.append('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public String toString (String separator) {
|
||||
return items.toString(separator);
|
||||
}
|
||||
|
||||
static public class OrderedSetIterator<T> extends ObjectSetIterator<T> {
|
||||
private Array<T> items;
|
||||
|
||||
public OrderedSetIterator (OrderedSet<T> set) {
|
||||
super(set);
|
||||
items = set.items;
|
||||
}
|
||||
|
||||
public void reset () {
|
||||
nextIndex = 0;
|
||||
hasNext = set.size > 0;
|
||||
}
|
||||
|
||||
public T next () {
|
||||
if (!hasNext) throw new NoSuchElementException();
|
||||
if (!valid) throw new RuntimeException("#iterator() cannot be used nested.");
|
||||
T key = items.get(nextIndex);
|
||||
nextIndex++;
|
||||
hasNext = nextIndex < set.size;
|
||||
return key;
|
||||
}
|
||||
|
||||
public void remove () {
|
||||
if (nextIndex < 0) throw new IllegalStateException("next must be called before remove.");
|
||||
nextIndex--;
|
||||
((OrderedSet)set).removeIndex(nextIndex);
|
||||
}
|
||||
}
|
||||
}
|
113
src/dorkbox/util/collections/Predicate.java
Normal file
113
src/dorkbox/util/collections/Predicate.java
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/** Interface used to select items within an iterator against a predicate.
|
||||
* @author Xoppa */
|
||||
public interface Predicate<T> {
|
||||
|
||||
/** @return true if the item matches the criteria and should be included in the iterator's items */
|
||||
boolean evaluate (T arg0);
|
||||
|
||||
public class PredicateIterator<T> implements Iterator<T> {
|
||||
public Iterator<T> iterator;
|
||||
public Predicate<T> predicate;
|
||||
public boolean end = false;
|
||||
public boolean peeked = false;
|
||||
public T next = null;
|
||||
|
||||
public PredicateIterator (final Iterable<T> iterable, final Predicate<T> predicate) {
|
||||
this(iterable.iterator(), predicate);
|
||||
}
|
||||
|
||||
public PredicateIterator (final Iterator<T> iterator, final Predicate<T> predicate) {
|
||||
set(iterator, predicate);
|
||||
}
|
||||
|
||||
public void set (final Iterable<T> iterable, final Predicate<T> predicate) {
|
||||
set(iterable.iterator(), predicate);
|
||||
}
|
||||
|
||||
public void set (final Iterator<T> iterator, final Predicate<T> predicate) {
|
||||
this.iterator = iterator;
|
||||
this.predicate = predicate;
|
||||
end = peeked = false;
|
||||
next = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext () {
|
||||
if (end) return false;
|
||||
if (next != null) return true;
|
||||
peeked = true;
|
||||
while (iterator.hasNext()) {
|
||||
final T n = iterator.next();
|
||||
if (predicate.evaluate(n)) {
|
||||
next = n;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
end = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next () {
|
||||
if (next == null && !hasNext()) return null;
|
||||
final T result = next;
|
||||
next = null;
|
||||
peeked = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove () {
|
||||
if (peeked) throw new RuntimeException("Cannot remove between a call to hasNext() and next().");
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PredicateIterable<T> implements Iterable<T> {
|
||||
public Iterable<T> iterable;
|
||||
public Predicate<T> predicate;
|
||||
public PredicateIterator<T> iterator = null;
|
||||
|
||||
public PredicateIterable (Iterable<T> iterable, Predicate<T> predicate) {
|
||||
set(iterable, predicate);
|
||||
}
|
||||
|
||||
public void set (Iterable<T> iterable, Predicate<T> predicate) {
|
||||
this.iterable = iterable;
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
/** Returns an iterator. Note that the same iterator instance is returned each time this method is called. Use the
|
||||
* {@link Predicate.PredicateIterator} constructor for nested or multithreaded iteration. */
|
||||
@Override
|
||||
public Iterator<T> iterator () {
|
||||
if (iterator == null)
|
||||
iterator = new PredicateIterator<T>(iterable.iterator(), predicate);
|
||||
else
|
||||
iterator.set(iterable.iterator(), predicate);
|
||||
return iterator;
|
||||
}
|
||||
}
|
||||
}
|
100
src/dorkbox/util/collections/QuickSelect.java
Normal file
100
src/dorkbox/util/collections/QuickSelect.java
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/** Implementation of Tony Hoare's quickselect algorithm. Running time is generally O(n), but worst case is O(n^2) Pivot choice is
|
||||
* median of three method, providing better performance than a random pivot for partially sorted data.
|
||||
* http://en.wikipedia.org/wiki/Quickselect
|
||||
* @author Jon Renner */
|
||||
public class QuickSelect<T> {
|
||||
private T[] array;
|
||||
private Comparator<? super T> comp;
|
||||
|
||||
public int select (T[] items, Comparator<T> comp, int n, int size) {
|
||||
this.array = items;
|
||||
this.comp = comp;
|
||||
return recursiveSelect(0, size - 1, n);
|
||||
}
|
||||
|
||||
private int partition (int left, int right, int pivot) {
|
||||
T pivotValue = array[pivot];
|
||||
swap(right, pivot);
|
||||
int storage = left;
|
||||
for (int i = left; i < right; i++) {
|
||||
if (comp.compare(array[i], pivotValue) < 0) {
|
||||
swap(storage, i);
|
||||
storage++;
|
||||
}
|
||||
}
|
||||
swap(right, storage);
|
||||
return storage;
|
||||
}
|
||||
|
||||
private int recursiveSelect (int left, int right, int k) {
|
||||
if (left == right) return left;
|
||||
int pivotIndex = medianOfThreePivot(left, right);
|
||||
int pivotNewIndex = partition(left, right, pivotIndex);
|
||||
int pivotDist = (pivotNewIndex - left) + 1;
|
||||
int result;
|
||||
if (pivotDist == k) {
|
||||
result = pivotNewIndex;
|
||||
} else if (k < pivotDist) {
|
||||
result = recursiveSelect(left, pivotNewIndex - 1, k);
|
||||
} else {
|
||||
result = recursiveSelect(pivotNewIndex + 1, right, k - pivotDist);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Median of Three has the potential to outperform a random pivot, especially for partially sorted arrays */
|
||||
private int medianOfThreePivot (int leftIdx, int rightIdx) {
|
||||
T left = array[leftIdx];
|
||||
int midIdx = (leftIdx + rightIdx) / 2;
|
||||
T mid = array[midIdx];
|
||||
T right = array[rightIdx];
|
||||
|
||||
// spaghetti median of three algorithm
|
||||
// does at most 3 comparisons
|
||||
if (comp.compare(left, mid) > 0) {
|
||||
if (comp.compare(mid, right) > 0) {
|
||||
return midIdx;
|
||||
} else if (comp.compare(left, right) > 0) {
|
||||
return rightIdx;
|
||||
} else {
|
||||
return leftIdx;
|
||||
}
|
||||
} else {
|
||||
if (comp.compare(left, right) > 0) {
|
||||
return leftIdx;
|
||||
} else if (comp.compare(mid, right) > 0) {
|
||||
return rightIdx;
|
||||
} else {
|
||||
return midIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void swap (int left, int right) {
|
||||
T tmp = array[left];
|
||||
array[left] = array[right];
|
||||
array[right] = tmp;
|
||||
}
|
||||
}
|
95
src/dorkbox/util/collections/Select.java
Normal file
95
src/dorkbox/util/collections/Select.java
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*******************************************************************************
|
||||
* Copyright 2011 LibGDX.
|
||||
* Mario Zechner <badlogicgames@gmail.com>
|
||||
* Nathan Sweet <nathan.sweet@gmail.com>
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/** This class is for selecting a ranked element (kth ordered statistic) from an unordered list in faster time than sorting the
|
||||
* whole array. Typical applications include finding the nearest enemy unit(s), and other operations which are likely to run as
|
||||
* often as every x frames. Certain values of k will result in a partial sorting of the Array.
|
||||
* <p>
|
||||
* The lowest ranking element starts at 1, not 0. 1 = first, 2 = second, 3 = third, etc. calling with a value of zero will result
|
||||
* in a {@link RuntimeException}
|
||||
* </p>
|
||||
* <p>
|
||||
* This class uses very minimal extra memory, as it makes no copies of the array. The underlying algorithms used are a naive
|
||||
* single-pass for k=min and k=max, and Hoare's quickselect for values in between.
|
||||
* </p>
|
||||
* @author Jon Renner */
|
||||
public class Select {
|
||||
private static Select instance;
|
||||
private QuickSelect quickSelect;
|
||||
|
||||
/** Provided for convenience */
|
||||
public static Select instance () {
|
||||
if (instance == null) instance = new Select();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public <T> T select (T[] items, Comparator<T> comp, int kthLowest, int size) {
|
||||
int idx = selectIndex(items, comp, kthLowest, size);
|
||||
return items[idx];
|
||||
}
|
||||
|
||||
public <T> int selectIndex (T[] items, Comparator<T> comp, int kthLowest, int size) {
|
||||
if (size < 1) {
|
||||
throw new RuntimeException("cannot select from empty array (size < 1)");
|
||||
} else if (kthLowest > size) {
|
||||
throw new RuntimeException("Kth rank is larger than size. k: " + kthLowest + ", size: " + size);
|
||||
}
|
||||
int idx;
|
||||
// naive partial selection sort almost certain to outperform quickselect where n is min or max
|
||||
if (kthLowest == 1) {
|
||||
// find min
|
||||
idx = fastMin(items, comp, size);
|
||||
} else if (kthLowest == size) {
|
||||
// find max
|
||||
idx = fastMax(items, comp, size);
|
||||
} else {
|
||||
// quickselect a better choice for cases of k between min and max
|
||||
if (quickSelect == null) quickSelect = new QuickSelect();
|
||||
idx = quickSelect.select(items, comp, kthLowest, size);
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/** Faster than quickselect for n = min */
|
||||
private <T> int fastMin (T[] items, Comparator<T> comp, int size) {
|
||||
int lowestIdx = 0;
|
||||
for (int i = 1; i < size; i++) {
|
||||
int comparison = comp.compare(items[i], items[lowestIdx]);
|
||||
if (comparison < 0) {
|
||||
lowestIdx = i;
|
||||
}
|
||||
}
|
||||
return lowestIdx;
|
||||
}
|
||||
|
||||
/** Faster than quickselect for n = max */
|
||||
private <T> int fastMax (T[] items, Comparator<T> comp, int size) {
|
||||
int highestIdx = 0;
|
||||
for (int i = 1; i < size; i++) {
|
||||
int comparison = comp.compare(items[i], items[highestIdx]);
|
||||
if (comparison > 0) {
|
||||
highestIdx = i;
|
||||
}
|
||||
}
|
||||
return highestIdx;
|
||||
}
|
||||
}
|
65
src/dorkbox/util/collections/Sort.java
Normal file
65
src/dorkbox/util/collections/Sort.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/** Provides methods to sort arrays of objects. Sorting requires working memory and this class allows that memory to be reused to
|
||||
* avoid allocation. The sorting is otherwise identical to the Arrays.sort methods (uses timsort).<br>
|
||||
* <br>
|
||||
* Note that sorting primitive arrays with the Arrays.sort methods does not allocate memory (unless sorting large arrays of char,
|
||||
* short, or byte).
|
||||
* @author Nathan Sweet */
|
||||
public class Sort {
|
||||
static private Sort instance;
|
||||
|
||||
private TimSort timSort;
|
||||
private ComparableTimSort comparableTimSort;
|
||||
|
||||
public <T> void sort (Array<T> a) {
|
||||
if (comparableTimSort == null) comparableTimSort = new ComparableTimSort();
|
||||
comparableTimSort.doSort((Object[])a.items, 0, a.size);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a) {
|
||||
if (comparableTimSort == null) comparableTimSort = new ComparableTimSort();
|
||||
comparableTimSort.doSort(a, 0, a.length);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a, int fromIndex, int toIndex) {
|
||||
if (comparableTimSort == null) comparableTimSort = new ComparableTimSort();
|
||||
comparableTimSort.doSort(a, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
public <T> void sort (Array<T> a, Comparator<? super T> c) {
|
||||
if (timSort == null) timSort = new TimSort();
|
||||
timSort.doSort((Object[])a.items, (Comparator)c, 0, a.size);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a, Comparator<? super T> c) {
|
||||
if (timSort == null) timSort = new TimSort();
|
||||
timSort.doSort(a, c, 0, a.length);
|
||||
}
|
||||
|
||||
public <T> void sort (T[] a, Comparator<? super T> c, int fromIndex, int toIndex) {
|
||||
if (timSort == null) timSort = new TimSort();
|
||||
timSort.doSort(a, c, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
/** Returns a Sort instance for convenience. Multiple threads must not use this instance at the same time. */
|
||||
static public Sort instance () {
|
||||
if (instance == null) instance = new Sort();
|
||||
return instance;
|
||||
}
|
||||
}
|
839
src/dorkbox/util/collections/TimSort.java
Normal file
839
src/dorkbox/util/collections/TimSort.java
Normal file
|
@ -0,0 +1,839 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package dorkbox.util.collections;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
/** A stable, adaptive, iterative mergesort that requires far fewer than n lg(n) comparisons when running on partially sorted
|
||||
* arrays, while offering performance comparable to a traditional mergesort when run on random arrays. Like all proper mergesorts,
|
||||
* this sort is stable and runs O(n log n) time (worst case). In the worst case, this sort requires temporary storage space for
|
||||
* n/2 object references; in the best case, it requires only a small constant amount of space.
|
||||
*
|
||||
* This implementation was adapted from Tim Peters's list sort for Python, which is described in detail here:
|
||||
*
|
||||
* http://svn.python.org/projects/python/trunk/Objects/listsort.txt
|
||||
*
|
||||
* Tim's C code may be found here:
|
||||
*
|
||||
* http://svn.python.org/projects/python/trunk/Objects/listobject.c
|
||||
*
|
||||
* The underlying techniques are described in this paper (and may have even earlier origins):
|
||||
*
|
||||
* "Optimistic Sorting and Information Theoretic Complexity" Peter McIlroy SODA (Fourth Annual ACM-SIAM Symposium on Discrete
|
||||
* Algorithms), pp 467-474, Austin, Texas, 25-27 January 1993.
|
||||
*
|
||||
* While the API to this class consists solely of static methods, it is (privately) instantiable; a TimSort instance holds the
|
||||
* state of an ongoing sort, assuming the input array is large enough to warrant the full-blown TimSort. Small arrays are sorted
|
||||
* in place, using a binary insertion sort. */
|
||||
class TimSort<T> {
|
||||
/** This is the minimum sized sequence that will be merged. Shorter sequences will be lengthened by calling binarySort. If the
|
||||
* entire array is less than this length, no merges will be performed.
|
||||
*
|
||||
* This constant should be a power of two. It was 64 in Tim Peter's C implementation, but 32 was empirically determined to work
|
||||
* better in this implementation. In the unlikely event that you set this constant to be a number that's not a power of two,
|
||||
* you'll need to change the {@link #minRunLength} computation.
|
||||
*
|
||||
* If you decrease this constant, you must change the stackLen computation in the TimSort constructor, or you risk an
|
||||
* ArrayOutOfBounds exception. See listsort.txt for a discussion of the minimum stack length required as a function of the
|
||||
* length of the array being sorted and the minimum merge sequence length. */
|
||||
private static final int MIN_MERGE = 32;
|
||||
|
||||
/** The array being sorted. */
|
||||
private T[] a;
|
||||
|
||||
/** The comparator for this sort. */
|
||||
private Comparator<? super T> c;
|
||||
|
||||
/** When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times. */
|
||||
private static final int MIN_GALLOP = 7;
|
||||
|
||||
/** This controls when we get *into* galloping mode. It is initialized to MIN_GALLOP. The mergeLo and mergeHi methods nudge it
|
||||
* higher for random data, and lower for highly structured data. */
|
||||
private int minGallop = MIN_GALLOP;
|
||||
|
||||
/** Maximum initial size of tmp array, which is used for merging. The array can grow to accommodate demand.
|
||||
*
|
||||
* Unlike Tim's original C version, we do not allocate this much storage when sorting smaller arrays. This change was required
|
||||
* for performance. */
|
||||
private static final int INITIAL_TMP_STORAGE_LENGTH = 256;
|
||||
|
||||
/** Temp storage for merges. */
|
||||
private T[] tmp; // Actual runtime type will be Object[], regardless of T
|
||||
private int tmpCount;
|
||||
|
||||
/** A stack of pending runs yet to be merged. Run i starts at address base[i] and extends for len[i] elements. It's always true
|
||||
* (so long as the indices are in bounds) that:
|
||||
*
|
||||
* runBase[i] + runLen[i] == runBase[i + 1]
|
||||
*
|
||||
* so we could cut the storage for this, but it's a minor amount, and keeping all the info explicit simplifies the code. */
|
||||
private int stackSize = 0; // Number of pending runs on stack
|
||||
private final int[] runBase;
|
||||
private final int[] runLen;
|
||||
|
||||
/** Asserts have been placed in if-statements for performance. To enable them, set this field to true and enable them in VM with
|
||||
* a command line flag. If you modify this class, please do test the asserts! */
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
TimSort () {
|
||||
tmp = (T[])new Object[INITIAL_TMP_STORAGE_LENGTH];
|
||||
runBase = new int[40];
|
||||
runLen = new int[40];
|
||||
}
|
||||
|
||||
public void doSort (T[] a, Comparator<T> c, int lo, int hi) {
|
||||
stackSize = 0;
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
binarySort(a, lo, hi, lo + initRunLen, c);
|
||||
return;
|
||||
}
|
||||
|
||||
this.a = a;
|
||||
this.c = c;
|
||||
tmpCount = 0;
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen, c);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
pushRun(lo, runLen);
|
||||
mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
mergeForceCollapse();
|
||||
if (DEBUG) assert stackSize == 1;
|
||||
|
||||
this.a = null;
|
||||
this.c = null;
|
||||
T[] tmp = this.tmp;
|
||||
for (int i = 0, n = tmpCount; i < n; i++)
|
||||
tmp[i] = null;
|
||||
}
|
||||
|
||||
/** Creates a TimSort instance to maintain the state of an ongoing sort.
|
||||
*
|
||||
* @param a the array to be sorted
|
||||
* @param c the comparator to determine the order of the sort */
|
||||
private TimSort (T[] a, Comparator<? super T> c) {
|
||||
this.a = a;
|
||||
this.c = c;
|
||||
|
||||
// Allocate temp storage (which may be increased later if necessary)
|
||||
int len = a.length;
|
||||
T[] newArray = (T[])new Object[len < 2 * INITIAL_TMP_STORAGE_LENGTH ? len >>> 1 : INITIAL_TMP_STORAGE_LENGTH];
|
||||
tmp = newArray;
|
||||
|
||||
/*
|
||||
* Allocate runs-to-be-merged stack (which cannot be expanded). The stack length requirements are described in listsort.txt.
|
||||
* The C version always uses the same stack length (85), but this was measured to be too expensive when sorting "mid-sized"
|
||||
* arrays (e.g., 100 elements) in Java. Therefore, we use smaller (but sufficiently large) stack lengths for smaller arrays.
|
||||
* The "magic numbers" in the computation below must be changed if MIN_MERGE is decreased. See the MIN_MERGE declaration
|
||||
* above for more information.
|
||||
*/
|
||||
int stackLen = (len < 120 ? 5 : len < 1542 ? 10 : len < 119151 ? 19 : 40);
|
||||
runBase = new int[stackLen];
|
||||
runLen = new int[stackLen];
|
||||
}
|
||||
|
||||
/*
|
||||
* The next two methods (which are package private and static) constitute the entire API of this class. Each of these methods
|
||||
* obeys the contract of the public method with the same signature in java.util.Arrays.
|
||||
*/
|
||||
|
||||
static <T> void sort (T[] a, Comparator<? super T> c) {
|
||||
sort(a, 0, a.length, c);
|
||||
}
|
||||
|
||||
static <T> void sort (T[] a, int lo, int hi, Comparator<? super T> c) {
|
||||
if (c == null) {
|
||||
Arrays.sort(a, lo, hi);
|
||||
return;
|
||||
}
|
||||
|
||||
rangeCheck(a.length, lo, hi);
|
||||
int nRemaining = hi - lo;
|
||||
if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted
|
||||
|
||||
// If array is small, do a "mini-TimSort" with no merges
|
||||
if (nRemaining < MIN_MERGE) {
|
||||
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
binarySort(a, lo, hi, lo + initRunLen, c);
|
||||
return;
|
||||
}
|
||||
|
||||
/** March over the array once, left to right, finding natural runs, extending short natural runs to minRun elements, and
|
||||
* merging runs to maintain stack invariant. */
|
||||
TimSort<T> ts = new TimSort<T>(a, c);
|
||||
int minRun = minRunLength(nRemaining);
|
||||
do {
|
||||
// Identify next run
|
||||
int runLen = countRunAndMakeAscending(a, lo, hi, c);
|
||||
|
||||
// If run is short, extend to min(minRun, nRemaining)
|
||||
if (runLen < minRun) {
|
||||
int force = nRemaining <= minRun ? nRemaining : minRun;
|
||||
binarySort(a, lo, lo + force, lo + runLen, c);
|
||||
runLen = force;
|
||||
}
|
||||
|
||||
// Push run onto pending-run stack, and maybe merge
|
||||
ts.pushRun(lo, runLen);
|
||||
ts.mergeCollapse();
|
||||
|
||||
// Advance to find next run
|
||||
lo += runLen;
|
||||
nRemaining -= runLen;
|
||||
} while (nRemaining != 0);
|
||||
|
||||
// Merge all remaining runs to complete sort
|
||||
if (DEBUG) assert lo == hi;
|
||||
ts.mergeForceCollapse();
|
||||
if (DEBUG) assert ts.stackSize == 1;
|
||||
}
|
||||
|
||||
/** Sorts the specified portion of the specified array using a binary insertion sort. This is the best method for sorting small
|
||||
* numbers of elements. It requires O(n log n) compares, but O(n^2) data movement (worst case).
|
||||
*
|
||||
* If the initial part of the specified range is already sorted, this method can take advantage of it: the method assumes that
|
||||
* the elements from index {@code lo}, inclusive, to {@code start}, exclusive are already sorted.
|
||||
*
|
||||
* @param a the array in which a range is to be sorted
|
||||
* @param lo the index of the first element in the range to be sorted
|
||||
* @param hi the index after the last element in the range to be sorted
|
||||
* @param start the index of the first element in the range that is not already known to be sorted (@code lo <= start <= hi}
|
||||
* @param c comparator to used for the sort */
|
||||
@SuppressWarnings("fallthrough")
|
||||
private static <T> void binarySort (T[] a, int lo, int hi, int start, Comparator<? super T> c) {
|
||||
if (DEBUG) assert lo <= start && start <= hi;
|
||||
if (start == lo) start++;
|
||||
for (; start < hi; start++) {
|
||||
T pivot = a[start];
|
||||
|
||||
// Set left (and right) to the index where a[start] (pivot) belongs
|
||||
int left = lo;
|
||||
int right = start;
|
||||
if (DEBUG) assert left <= right;
|
||||
/*
|
||||
* Invariants: pivot >= all in [lo, left). pivot < all in [right, start).
|
||||
*/
|
||||
while (left < right) {
|
||||
int mid = (left + right) >>> 1;
|
||||
if (c.compare(pivot, a[mid]) < 0)
|
||||
right = mid;
|
||||
else
|
||||
left = mid + 1;
|
||||
}
|
||||
if (DEBUG) assert left == right;
|
||||
|
||||
/*
|
||||
* The invariants still hold: pivot >= all in [lo, left) and pivot < all in [left, start), so pivot belongs at left. Note
|
||||
* that if there are elements equal to pivot, left points to the first slot after them -- that's why this sort is stable.
|
||||
* Slide elements over to make room for pivot.
|
||||
*/
|
||||
int n = start - left; // The number of elements to move
|
||||
// Switch is just an optimization for arraycopy in default case
|
||||
switch (n) {
|
||||
case 2:
|
||||
a[left + 2] = a[left + 1];
|
||||
case 1:
|
||||
a[left + 1] = a[left];
|
||||
break;
|
||||
default:
|
||||
System.arraycopy(a, left, a, left + 1, n);
|
||||
}
|
||||
a[left] = pivot;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the length of the run beginning at the specified position in the specified array and reverses the run if it is
|
||||
* descending (ensuring that the run will always be ascending when the method returns).
|
||||
*
|
||||
* A run is the longest ascending sequence with:
|
||||
*
|
||||
* a[lo] <= a[lo + 1] <= a[lo + 2] <= ...
|
||||
*
|
||||
* or the longest descending sequence with:
|
||||
*
|
||||
* a[lo] > a[lo + 1] > a[lo + 2] > ...
|
||||
*
|
||||
* For its intended use in a stable mergesort, the strictness of the definition of "descending" is needed so that the call can
|
||||
* safely reverse a descending sequence without violating stability.
|
||||
*
|
||||
* @param a the array in which a run is to be counted and possibly reversed
|
||||
* @param lo index of the first element in the run
|
||||
* @param hi index after the last element that may be contained in the run. It is required that @code{lo < hi}.
|
||||
* @param c the comparator to used for the sort
|
||||
* @return the length of the run beginning at the specified position in the specified array */
|
||||
private static <T> int countRunAndMakeAscending (T[] a, int lo, int hi, Comparator<? super T> c) {
|
||||
if (DEBUG) assert lo < hi;
|
||||
int runHi = lo + 1;
|
||||
if (runHi == hi) return 1;
|
||||
|
||||
// Find end of run, and reverse range if descending
|
||||
if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
|
||||
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
|
||||
runHi++;
|
||||
reverseRange(a, lo, runHi);
|
||||
} else { // Ascending
|
||||
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
|
||||
runHi++;
|
||||
}
|
||||
|
||||
return runHi - lo;
|
||||
}
|
||||
|
||||
/** Reverse the specified range of the specified array.
|
||||
*
|
||||
* @param a the array in which a range is to be reversed
|
||||
* @param lo the index of the first element in the range to be reversed
|
||||
* @param hi the index after the last element in the range to be reversed */
|
||||
private static void reverseRange (Object[] a, int lo, int hi) {
|
||||
hi--;
|
||||
while (lo < hi) {
|
||||
Object t = a[lo];
|
||||
a[lo++] = a[hi];
|
||||
a[hi--] = t;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the minimum acceptable run length for an array of the specified length. Natural runs shorter than this will be
|
||||
* extended with {@link #binarySort}.
|
||||
*
|
||||
* Roughly speaking, the computation is:
|
||||
*
|
||||
* If n < MIN_MERGE, return n (it's too small to bother with fancy stuff). Else if n is an exact power of 2, return
|
||||
* MIN_MERGE/2. Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k is close to, but strictly less than, an
|
||||
* exact power of 2.
|
||||
*
|
||||
* For the rationale, see listsort.txt.
|
||||
*
|
||||
* @param n the length of the array to be sorted
|
||||
* @return the length of the minimum run to be merged */
|
||||
private static int minRunLength (int n) {
|
||||
if (DEBUG) assert n >= 0;
|
||||
int r = 0; // Becomes 1 if any 1 bits are shifted off
|
||||
while (n >= MIN_MERGE) {
|
||||
r |= (n & 1);
|
||||
n >>= 1;
|
||||
}
|
||||
return n + r;
|
||||
}
|
||||
|
||||
/** Pushes the specified run onto the pending-run stack.
|
||||
*
|
||||
* @param runBase index of the first element in the run
|
||||
* @param runLen the number of elements in the run */
|
||||
private void pushRun (int runBase, int runLen) {
|
||||
this.runBase[stackSize] = runBase;
|
||||
this.runLen[stackSize] = runLen;
|
||||
stackSize++;
|
||||
}
|
||||
|
||||
/** Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished:
|
||||
*
|
||||
* 1. runLen[n - 2] > runLen[n - 1] + runLen[n] 2. runLen[n - 1] > runLen[n]
|
||||
*
|
||||
* where n is the index of the last run in runLen.
|
||||
*
|
||||
* This method has been formally verified to be correct after checking the last 4 runs.
|
||||
* Checking for 3 runs results in an exception for large arrays.
|
||||
* (Source: http://envisage-project.eu/proving-android-java-and-python-sorting-algorithm-is-broken-and-how-to-fix-it/)
|
||||
*
|
||||
* This method is called each time a new run is pushed onto the stack, so the invariants are guaranteed to hold for i <
|
||||
* stackSize upon entry to the method. */
|
||||
private void mergeCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if ((n >= 1 && runLen[n - 1] <= runLen[n] + runLen[n + 1]) || (n >= 2 && runLen[n - 2] <= runLen[n] + runLen[n - 1])) {
|
||||
if (runLen[n - 1] < runLen[n + 1]) n--;
|
||||
} else if (runLen[n] > runLen[n + 1]) {
|
||||
break; // Invariant is established
|
||||
}
|
||||
mergeAt(n);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges all runs on the stack until only one remains. This method is called once, to complete the sort. */
|
||||
private void mergeForceCollapse () {
|
||||
while (stackSize > 1) {
|
||||
int n = stackSize - 2;
|
||||
if (n > 0 && runLen[n - 1] < runLen[n + 1]) n--;
|
||||
mergeAt(n);
|
||||
}
|
||||
}
|
||||
|
||||
/** Merges the two runs at stack indices i and i+1. Run i must be the penultimate or antepenultimate run on the stack. In other
|
||||
* words, i must be equal to stackSize-2 or stackSize-3.
|
||||
*
|
||||
* @param i stack index of the first of the two runs to merge */
|
||||
private void mergeAt (int i) {
|
||||
if (DEBUG) assert stackSize >= 2;
|
||||
if (DEBUG) assert i >= 0;
|
||||
if (DEBUG) assert i == stackSize - 2 || i == stackSize - 3;
|
||||
|
||||
int base1 = runBase[i];
|
||||
int len1 = runLen[i];
|
||||
int base2 = runBase[i + 1];
|
||||
int len2 = runLen[i + 1];
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0;
|
||||
if (DEBUG) assert base1 + len1 == base2;
|
||||
|
||||
/*
|
||||
* Record the length of the combined runs; if i is the 3rd-last run now, also slide over the last run (which isn't involved
|
||||
* in this merge). The current run (i+1) goes away in any case.
|
||||
*/
|
||||
runLen[i] = len1 + len2;
|
||||
if (i == stackSize - 3) {
|
||||
runBase[i + 1] = runBase[i + 2];
|
||||
runLen[i + 1] = runLen[i + 2];
|
||||
}
|
||||
stackSize--;
|
||||
|
||||
/*
|
||||
* Find where the first element of run2 goes in run1. Prior elements in run1 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
int k = gallopRight(a[base2], a, base1, len1, 0, c);
|
||||
if (DEBUG) assert k >= 0;
|
||||
base1 += k;
|
||||
len1 -= k;
|
||||
if (len1 == 0) return;
|
||||
|
||||
/*
|
||||
* Find where the last element of run1 goes in run2. Subsequent elements in run2 can be ignored (because they're already in
|
||||
* place).
|
||||
*/
|
||||
len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c);
|
||||
if (DEBUG) assert len2 >= 0;
|
||||
if (len2 == 0) return;
|
||||
|
||||
// Merge remaining runs, using tmp array with min(len1, len2) elements
|
||||
if (len1 <= len2)
|
||||
mergeLo(base1, len1, base2, len2);
|
||||
else
|
||||
mergeHi(base1, len1, base2, len2);
|
||||
}
|
||||
|
||||
/** Locates the position at which to insert the specified key into the specified sorted range; if the range contains an element
|
||||
* equal to key, returns the index of the leftmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @param c the comparator used to order the range, and to search
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], pretending that a[b - 1] is minus infinity and a[b
|
||||
* + n] is infinity. In other words, key belongs at index b + k; or in other words, the first k elements of a should
|
||||
* precede key, and the last n - k should follow it. */
|
||||
private static <T> int gallopLeft (T key, T[] a, int base, int len, int hint, Comparator<? super T> c) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
int lastOfs = 0;
|
||||
int ofs = 1;
|
||||
if (c.compare(key, a[base + hint]) > 0) {
|
||||
// Gallop right until a[base+hint+lastOfs] < key <= a[base+hint+ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) > 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
} else { // key <= a[base + hint]
|
||||
// Gallop left until a[base+hint-ofs] < key <= a[base+hint-lastOfs]
|
||||
final int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) <= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to base
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[base+lastOfs] < key <= a[base+ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[base + lastOfs - 1] < key <= a[base + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (c.compare(key, a[base + m]) > 0)
|
||||
lastOfs = m + 1; // a[base + m] < key
|
||||
else
|
||||
ofs = m; // key <= a[base + m]
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[base + ofs - 1] < key <= a[base + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Like gallopLeft, except that if the range contains an element equal to key, gallopRight returns the index after the
|
||||
* rightmost equal element.
|
||||
*
|
||||
* @param key the key whose insertion point to search for
|
||||
* @param a the array in which to search
|
||||
* @param base the index of the first element in the range
|
||||
* @param len the length of the range; must be > 0
|
||||
* @param hint the index at which to begin the search, 0 <= hint < n. The closer hint is to the result, the faster this method
|
||||
* will run.
|
||||
* @param c the comparator used to order the range, and to search
|
||||
* @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] */
|
||||
private static <T> int gallopRight (T key, T[] a, int base, int len, int hint, Comparator<? super T> c) {
|
||||
if (DEBUG) assert len > 0 && hint >= 0 && hint < len;
|
||||
|
||||
int ofs = 1;
|
||||
int lastOfs = 0;
|
||||
if (c.compare(key, a[base + hint]) < 0) {
|
||||
// Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs]
|
||||
int maxOfs = hint + 1;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) < 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
int tmp = lastOfs;
|
||||
lastOfs = hint - ofs;
|
||||
ofs = hint - tmp;
|
||||
} else { // a[b + hint] <= key
|
||||
// Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs]
|
||||
int maxOfs = len - hint;
|
||||
while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) >= 0) {
|
||||
lastOfs = ofs;
|
||||
ofs = (ofs << 1) + 1;
|
||||
if (ofs <= 0) // int overflow
|
||||
ofs = maxOfs;
|
||||
}
|
||||
if (ofs > maxOfs) ofs = maxOfs;
|
||||
|
||||
// Make offsets relative to b
|
||||
lastOfs += hint;
|
||||
ofs += hint;
|
||||
}
|
||||
if (DEBUG) assert -1 <= lastOfs && lastOfs < ofs && ofs <= len;
|
||||
|
||||
/*
|
||||
* Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to the right of lastOfs but no farther right than ofs.
|
||||
* Do a binary search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs].
|
||||
*/
|
||||
lastOfs++;
|
||||
while (lastOfs < ofs) {
|
||||
int m = lastOfs + ((ofs - lastOfs) >>> 1);
|
||||
|
||||
if (c.compare(key, a[base + m]) < 0)
|
||||
ofs = m; // key < a[b + m]
|
||||
else
|
||||
lastOfs = m + 1; // a[b + m] <= key
|
||||
}
|
||||
if (DEBUG) assert lastOfs == ofs; // so a[b + ofs - 1] <= key < a[b + ofs]
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/** Merges two adjacent runs in place, in a stable fashion. The first element of the first run must be greater than the first
|
||||
* element of the second run (a[base1] > a[base2]), and the last element of the first run (a[base1 + len1-1]) must be greater
|
||||
* than all elements of the second run.
|
||||
*
|
||||
* For performance, this method should be called only when len1 <= len2; its twin, mergeHi should be called if len1 >= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
private void mergeLo (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy first run into temp array
|
||||
T[] a = this.a; // For performance
|
||||
T[] tmp = ensureCapacity(len1);
|
||||
System.arraycopy(a, base1, tmp, 0, len1);
|
||||
|
||||
int cursor1 = 0; // Indexes into tmp array
|
||||
int cursor2 = base2; // Indexes int a
|
||||
int dest = base1; // Indexes int a
|
||||
|
||||
// Move first element of second run and deal with degenerate cases
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
return;
|
||||
}
|
||||
if (len1 == 1) {
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
return;
|
||||
}
|
||||
|
||||
Comparator<? super T> c = this.c; // Use local variable for performance
|
||||
int minGallop = this.minGallop; // " " " " "
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run starts winning consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
if (c.compare(a[cursor2], tmp[cursor1]) < 0) {
|
||||
a[dest++] = a[cursor2++];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 0) break outer;
|
||||
} else {
|
||||
a[dest++] = tmp[cursor1++];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 1 && len2 > 0;
|
||||
count1 = gallopRight(a[cursor2], tmp, cursor1, len1, 0, c);
|
||||
if (count1 != 0) {
|
||||
System.arraycopy(tmp, cursor1, a, dest, count1);
|
||||
dest += count1;
|
||||
cursor1 += count1;
|
||||
len1 -= count1;
|
||||
if (len1 <= 1) // len1 == 1 || len1 == 0
|
||||
break outer;
|
||||
}
|
||||
a[dest++] = a[cursor2++];
|
||||
if (--len2 == 0) break outer;
|
||||
|
||||
count2 = gallopLeft(tmp[cursor1], a, cursor2, len2, 0, c);
|
||||
if (count2 != 0) {
|
||||
System.arraycopy(a, cursor2, a, dest, count2);
|
||||
dest += count2;
|
||||
cursor2 += count2;
|
||||
len2 -= count2;
|
||||
if (len2 == 0) break outer;
|
||||
}
|
||||
a[dest++] = tmp[cursor1++];
|
||||
if (--len1 == 1) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len1 == 1) {
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(a, cursor2, a, dest, len2);
|
||||
a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
|
||||
} else if (len1 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len2 == 0;
|
||||
if (DEBUG) assert len1 > 1;
|
||||
System.arraycopy(tmp, cursor1, a, dest, len1);
|
||||
}
|
||||
}
|
||||
|
||||
/** Like mergeLo, except that this method should be called only if len1 >= len2; mergeLo should be called if len1 <= len2.
|
||||
* (Either method may be called if len1 == len2.)
|
||||
*
|
||||
* @param base1 index of first element in first run to be merged
|
||||
* @param len1 length of first run to be merged (must be > 0)
|
||||
* @param base2 index of first element in second run to be merged (must be aBase + aLen)
|
||||
* @param len2 length of second run to be merged (must be > 0) */
|
||||
private void mergeHi (int base1, int len1, int base2, int len2) {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 0 && base1 + len1 == base2;
|
||||
|
||||
// Copy second run into temp array
|
||||
T[] a = this.a; // For performance
|
||||
T[] tmp = ensureCapacity(len2);
|
||||
System.arraycopy(a, base2, tmp, 0, len2);
|
||||
|
||||
int cursor1 = base1 + len1 - 1; // Indexes into a
|
||||
int cursor2 = len2 - 1; // Indexes into tmp array
|
||||
int dest = base2 + len2 - 1; // Indexes into a
|
||||
|
||||
// Move last element of first run and deal with degenerate cases
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) {
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
return;
|
||||
}
|
||||
if (len2 == 1) {
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2];
|
||||
return;
|
||||
}
|
||||
|
||||
Comparator<? super T> c = this.c; // Use local variable for performance
|
||||
int minGallop = this.minGallop; // " " " " "
|
||||
outer:
|
||||
while (true) {
|
||||
int count1 = 0; // Number of times in a row that first run won
|
||||
int count2 = 0; // Number of times in a row that second run won
|
||||
|
||||
/*
|
||||
* Do the straightforward thing until (if ever) one run appears to win consistently.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
if (c.compare(tmp[cursor2], a[cursor1]) < 0) {
|
||||
a[dest--] = a[cursor1--];
|
||||
count1++;
|
||||
count2 = 0;
|
||||
if (--len1 == 0) break outer;
|
||||
} else {
|
||||
a[dest--] = tmp[cursor2--];
|
||||
count2++;
|
||||
count1 = 0;
|
||||
if (--len2 == 1) break outer;
|
||||
}
|
||||
} while ((count1 | count2) < minGallop);
|
||||
|
||||
/*
|
||||
* One run is winning so consistently that galloping may be a huge win. So try that, and continue galloping until (if
|
||||
* ever) neither run appears to be winning consistently anymore.
|
||||
*/
|
||||
do {
|
||||
if (DEBUG) assert len1 > 0 && len2 > 1;
|
||||
count1 = len1 - gallopRight(tmp[cursor2], a, base1, len1, len1 - 1, c);
|
||||
if (count1 != 0) {
|
||||
dest -= count1;
|
||||
cursor1 -= count1;
|
||||
len1 -= count1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, count1);
|
||||
if (len1 == 0) break outer;
|
||||
}
|
||||
a[dest--] = tmp[cursor2--];
|
||||
if (--len2 == 1) break outer;
|
||||
|
||||
count2 = len2 - gallopLeft(a[cursor1], tmp, 0, len2, len2 - 1, c);
|
||||
if (count2 != 0) {
|
||||
dest -= count2;
|
||||
cursor2 -= count2;
|
||||
len2 -= count2;
|
||||
System.arraycopy(tmp, cursor2 + 1, a, dest + 1, count2);
|
||||
if (len2 <= 1) // len2 == 1 || len2 == 0
|
||||
break outer;
|
||||
}
|
||||
a[dest--] = a[cursor1--];
|
||||
if (--len1 == 0) break outer;
|
||||
minGallop--;
|
||||
} while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP);
|
||||
if (minGallop < 0) minGallop = 0;
|
||||
minGallop += 2; // Penalize for leaving gallop mode
|
||||
} // End of "outer" loop
|
||||
this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
|
||||
|
||||
if (len2 == 1) {
|
||||
if (DEBUG) assert len1 > 0;
|
||||
dest -= len1;
|
||||
cursor1 -= len1;
|
||||
System.arraycopy(a, cursor1 + 1, a, dest + 1, len1);
|
||||
a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge
|
||||
} else if (len2 == 0) {
|
||||
throw new IllegalArgumentException("Comparison method violates its general contract!");
|
||||
} else {
|
||||
if (DEBUG) assert len1 == 0;
|
||||
if (DEBUG) assert len2 > 0;
|
||||
System.arraycopy(tmp, 0, a, dest - (len2 - 1), len2);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensures that the external array tmp has at least the specified number of elements, increasing its size if necessary. The
|
||||
* size increases exponentially to ensure amortized linear time complexity.
|
||||
*
|
||||
* @param minCapacity the minimum required capacity of the tmp array
|
||||
* @return tmp, whether or not it grew */
|
||||
private T[] ensureCapacity (int minCapacity) {
|
||||
tmpCount = Math.max(tmpCount, minCapacity);
|
||||
if (tmp.length < minCapacity) {
|
||||
// Compute smallest power of 2 > minCapacity
|
||||
int newSize = minCapacity;
|
||||
newSize |= newSize >> 1;
|
||||
newSize |= newSize >> 2;
|
||||
newSize |= newSize >> 4;
|
||||
newSize |= newSize >> 8;
|
||||
newSize |= newSize >> 16;
|
||||
newSize++;
|
||||
|
||||
if (newSize < 0) // Not bloody likely!
|
||||
newSize = minCapacity;
|
||||
else
|
||||
newSize = Math.min(newSize, a.length >>> 1);
|
||||
|
||||
T[] newArray = (T[])new Object[newSize];
|
||||
tmp = newArray;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/** Checks that fromIndex and toIndex are in range, and throws an appropriate exception if they aren't.
|
||||
*
|
||||
* @param arrayLen the length of the array
|
||||
* @param fromIndex the index of the first element of the range
|
||||
* @param toIndex the index after the last element of the range
|
||||
* @throws IllegalArgumentException if fromIndex > toIndex
|
||||
* @throws ArrayIndexOutOfBoundsException if fromIndex < 0 or toIndex > arrayLen */
|
||||
private static void rangeCheck (int arrayLen, int fromIndex, int toIndex) {
|
||||
if (fromIndex > toIndex) throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
|
||||
if (fromIndex < 0) throw new ArrayIndexOutOfBoundsException(fromIndex);
|
||||
if (toIndex > arrayLen) throw new ArrayIndexOutOfBoundsException(toIndex);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user