Added the rest of the collection utilities, updated to latest

This commit is contained in:
nathan 2019-03-25 16:13:27 +01:00
parent dda9d08815
commit 22152200b9
26 changed files with 12860 additions and 820 deletions

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}
}

View File

@ -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);
}
}

View 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;
}
}
}

View 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

View 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;
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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();
}
}
}

View File

@ -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();
}
}
}

View 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;
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}