/* * Copyright 2023 dorkbox, llc * * 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. */ /******************************************************************************* * Copyright 2011 LibGDX. * Mario Zechner @gmail.com> * 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.collections import dorkbox.collections.Collections.allocateIterators import dorkbox.collections.Collections.random import dorkbox.collections.Predicate.PredicateIterable import java.util.* import kotlin.math.max import kotlin.math.min /** * 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 */ class ExpandingArray : MutableIterable { /** * Provides direct access to the underlying array. If the Array's generic type is not Object, this field may only be accessed * if the [ExpandingArray.Array] constructor was used. */ var items: Array var size = 0 var ordered: Boolean private var iterable: ArrayIterable? = null private var predicateIterable: PredicateIterable? = null /** * Creates an ordered array with the specified capacity. */ constructor(capacity: Int) : this(true, capacity) /** * Creates a new array with [.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. */ constructor(ordered: Boolean = true, capacity: Int = 16) { this.ordered = ordered @Suppress("UNCHECKED_CAST") items = arrayOfNulls(capacity) as Array } /** * Creates a new array with [.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. */ constructor(ordered: Boolean, capacity: Int, arrayType: Class) { this.ordered = ordered @Suppress("UNCHECKED_CAST") items = java.lang.reflect.Array.newInstance(arrayType, capacity) as Array } /** * Creates an ordered array with [.items] of the specified type and a capacity of 16. */ constructor(arrayType: Class) : 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. */ @Suppress("UNCHECKED_CAST") constructor(array: ExpandingArray) : this(array.ordered, array.size, array.items.javaClass.componentType as Class) { 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. */ constructor(array: Array) : this(true, array, 0, array.size) /** * 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. */ @Suppress("UNCHECKED_CAST") constructor(ordered: Boolean, array: Array, start: Int, count: Int) : this(ordered, count, array.javaClass.componentType as Class) { size = count System.arraycopy(array, start, items, 0, size) } fun add(value: T) { var items = items if (size == items.size) items = resize(max(8.0, (size * 1.75f).toInt().toDouble()).toInt()) items[size++] = value } fun add(value1: T, value2: T) { var items = items if (size + 1 >= items.size) items = resize(max(8.0, (size * 1.75f).toInt().toDouble()).toInt()) items[size] = value1 items[size + 1] = value2 size += 2 } fun add(value1: T, value2: T, value3: T) { var items = items if (size + 2 >= items.size) items = resize(max(8.0, (size * 1.75f).toInt().toDouble()).toInt()) items[size] = value1 items[size + 1] = value2 items[size + 2] = value3 size += 3 } fun add(value1: T, value2: T, value3: T, value4: T) { var items = items if (size + 3 >= items.size) items = resize(max(8.0, (size * 1.8f).toInt().toDouble()).toInt()) // 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 } fun addAll(array: ExpandingArray) { @Suppress("UNCHECKED_CAST") addAll(array.items as Array, 0, array.size) } fun addAll(array: ExpandingArray, start: Int, count: Int) { if (start + count > array.size) { throw StateException("start + count must be <= size: $start + $count <= ${array.size}") } @Suppress("UNCHECKED_CAST") addAll(array.items as Array, start, count) } fun addAll(vararg array: T) { @Suppress("UNCHECKED_CAST") addAll(array as Array, 0, array.size) } fun addAll(array: Array, start: Int, count: Int) { var items = items val sizeNeeded = size + count if (sizeNeeded > items.size) items = resize( max(max(8.0, sizeNeeded.toDouble()), (size * 1.75f).toInt().toDouble()).toInt() ) System.arraycopy(array, start, items, size, count) size = sizeNeeded } operator fun get(index: Int): T { if (index >= size) throw IndexOutOfBoundsException("index can't be >= size: $index >= $size") return items[index]!! } operator fun set(index: Int, value: T) { if (index >= size) throw IndexOutOfBoundsException("index can't be >= size: $index >= $size") items[index] = value } fun insert(index: Int, value: T) { if (index > size) throw IndexOutOfBoundsException("index can't be > size: $index > $size") var items = items if (size == items.size) items = resize(max(8.0, (size * 1.75f).toInt().toDouble()).toInt()) if (ordered) System.arraycopy(items, index, items, index + 1, size - index) else items[size] = items[index] size++ items[index] = value } /** * Inserts the specified number of items at the specified index. The new items will have values equal to the values at those * indices before the insertion. */ fun insertRange(index: Int, count: Int) { if (index > size) throw IndexOutOfBoundsException("index can't be > size: $index > $size") val sizeNeeded = size + count if (sizeNeeded > items.size) items = resize( max(max(8.0, sizeNeeded.toDouble()), (size * 1.75f).toInt().toDouble()).toInt() ) System.arraycopy(items, index, items, index + count, size - index) size = sizeNeeded } fun swap(first: Int, second: Int) { if (first >= size) throw IndexOutOfBoundsException("first can't be >= size: $first >= $size") if (second >= size) throw IndexOutOfBoundsException("second can't be >= size: $second >= $size") val items = items val firstValue = items[first] items[first] = items[second] items[second] = firstValue } /** * Returns true if this array contains the specified value. * * @param value May be null. * @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */ fun contains(value: T?, identity: Boolean): Boolean { val items = items var i = size - 1 if (identity || value == null) { while (i >= 0) if (items[i--] === value) return true } else { while (i >= 0) if (value == items[i--]) return true } return false } /** * Returns true if this array contains all the specified values. * * @param values May contains nulls. * @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */ fun containsAll(values: ExpandingArray, identity: Boolean): Boolean { val items = values.items var i = 0 val n = values.size while (i < n) { if (!contains(items[i], identity)) return false i++ } return true } /** * Returns true if this array contains any the specified values. * * @param values May contains nulls. * @param identity If true, == comparison will be used. If false, .equals() comparison will be used. */ fun containsAny(values: ExpandingArray, identity: Boolean): Boolean { val items = values.items var i = 0 val n = values.size while (i < n) { if (contains(items[i], identity)) return true i++ } 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 */ fun indexOf(value: T?, identity: Boolean): Int { val items = items if (identity || value == null) { var i = 0 val n = size while (i < n) { if (items[i] === value) return i i++ } } else { var i = 0 val n = size while (i < n) { if (value == items[i]) return i 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 */ fun lastIndexOf(value: T?, identity: Boolean): Int { val items = items if (identity || value == null) { for (i in size - 1 downTo 0) if (items[i] === value) return i } else { for (i in size - 1 downTo 0) if (value == 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 */ fun removeValue(value: T?, identity: Boolean): Boolean { val items = items if (identity || value == null) { var i = 0 val n = size while (i < n) { if (items[i] === value) { removeIndex(i) return true } i++ } } else { var i = 0 val n = size while (i < n) { if (value == items[i]) { removeIndex(i) return true } i++ } } return false } /** * Removes and returns the item at the specified index. */ fun removeIndex(index: Int): T? { if (index >= size) throw IndexOutOfBoundsException("index can't be >= size: $index >= $size") val items = items val value = 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. */ fun removeRange(start: Int, end: Int) { val n = size if (end >= n) throw IndexOutOfBoundsException("end can't be >= size: $end >= $size") if (start > end) throw IndexOutOfBoundsException("start can't be > end: $start > $end") val items = items val count = end - start + 1 val lastIndex = n - count if (ordered) System.arraycopy(items, start + count, items, start, n - (start + count)) else { val i = max(lastIndex.toDouble(), (end + 1).toDouble()).toInt() System.arraycopy(items, i, items, start, n - i) } for (i in lastIndex until n) items[i] = null size = n - count } /** * Removes from this array all elements contained in the specified array. * * @param identity True to use ==, false to use .equals(). * * @return true if this array was modified. */ fun removeAll(array: ExpandingArray, identity: Boolean): Boolean { var size = size val startSize = size val items = items if (identity) { var i = 0 val n = array.size while (i < n) { val item = array[i] for (ii in 0 until size) { if (item === items[ii]) { removeIndex(ii) size-- break } } i++ } } else { var i = 0 val n = array.size while (i < n) { val item = array[i] for (ii in 0 until size) { if (item == items[ii]) { removeIndex(ii) size-- break } } i++ } } return size != startSize } /** * Removes and returns the last item. */ fun pop(): T? { check(size != 0) { "Array is empty." } --size val item = items[size] items[size] = null return item } /** * Returns the last item. */ fun peek(): T? { check(size != 0) { "Array is empty." } return items[size - 1] } /** * Returns the first item. */ fun first(): T? { check(size != 0) { "Array is empty." } return items[0] } /** * Returns true if the array has one or more items. */ fun notEmpty(): Boolean { return size > 0 } /** * Returns true if the array is empty. */ fun isEmpty(): Boolean { return size == 0 } fun clear() { Arrays.fill(items, 0, size, 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 [.items] */ fun shrink(): Array { if (items.size != 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 [.items] */ fun ensureCapacity(additionalCapacity: Int): Array { if (additionalCapacity < 0) { throw StateException("additionalCapacity must be >= 0: $additionalCapacity") } val sizeNeeded = size + additionalCapacity if (sizeNeeded > items.size) resize( max(max(8.0, sizeNeeded.toDouble()), (size * 1.75f).toInt().toDouble()).toInt() ) return items } /** * Sets the array size, leaving any values beyond the current size null. * * @return [.items] */ fun setSize(newSize: Int): Array { truncate(newSize) if (newSize > items.size) resize(max(8.0, newSize.toDouble()).toInt()) size = newSize return items } /** * Creates a new backing array with the specified size containing the current items. */ protected fun resize(newSize: Int): Array { val items = items @Suppress("UNCHECKED_CAST") val newItems = java.lang.reflect.Array.newInstance(items.javaClass.componentType, newSize) as Array System.arraycopy(items, 0, newItems, 0, min(size.toDouble(), newItems.size.toDouble()).toInt()) this.items = newItems return newItems } /** * Sorts this array. The array elements must implement [Comparable]. This method is not thread safe */ fun sort() { items.sort(0, size) } /** * Sorts the array. This method is not thread safe. */ fun sort(comparator: Comparator) { items.sortWith(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 [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. */ fun selectRanked(comparator: Comparator, kthLowest: Int): T? { if (kthLowest < 1) { throw RuntimeException("nth_lowest must be greater than 0, 1 = first, 2 = second...") } return Select.instance().select(items, comparator, kthLowest, size) } /** * @see ExpandingArray.selectRanked * * @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. */ fun selectRankedIndex(comparator: Comparator, kthLowest: Int): Int { if (kthLowest < 1) { throw RuntimeException("nth_lowest must be greater than 0, 1 = first, 2 = second...") } return Select.instance().selectIndex(items, comparator, kthLowest, size) } fun reverse() { val items = items var i = 0 val lastIndex = size - 1 val n = size / 2 while (i < n) { val ii = lastIndex - i val temp = items[i] items[i] = items[ii] items[ii] = temp i++ } } fun shuffle() { val items = items for (i in size - 1 downTo 0) { val ii = random(i) val temp = items[i] items[i] = items[ii] items[ii] = temp } } /** * Returns an iterator for the items in the array. Remove is supported. * * If [Collections.allocateIterators] is false, the same iterator instance is returned each time this method is called. * * Use the [ArrayIterator] constructor for nested or multithreaded iteration. */ override fun iterator(): ArrayIterator { if (allocateIterators) return ArrayIterator(this, true) if (iterable == null) iterable = ArrayIterable(this) return iterable!!.iterator() } /** * Returns an iterable for the selected items in the array. Remove is supported, but not between hasNext() and next(). * * If [Collections.allocateIterators] is false, the same iterable instance is returned each time this method is called. * * Use the [Predicate.PredicateIterable] constructor for nested or multithreaded iteration. */ fun select(predicate: Predicate?): Iterable { if (allocateIterators) return PredicateIterable(this, predicate) if (predicateIterable == null) { predicateIterable = PredicateIterable(this, predicate) } else { predicateIterable!![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. */ fun truncate(newSize: Int) { if (newSize < 0) { throw StateException("newSize must be >= 0: $newSize") } if (size <= newSize) return for (i in newSize until size) items[i] = null size = newSize } /** * Returns a random item from the array, or null if the array is empty. */ fun random(): T? { return if (size == 0) null else items[random(0, size - 1)] } /** * Returns the items as an array. Note the array is typed, so the [.Array] constructor must have been used. * Otherwise use [.toArray] to specify the array type. */ fun toArray(): Array { @Suppress("UNCHECKED_CAST") return toArray(items.javaClass.componentType) as Array } fun toArray(type: Class?): Array { @Suppress("UNCHECKED_CAST") val result = java.lang.reflect.Array.newInstance(type, size) as Array System.arraycopy(items, 0, result, 0, size) return result } override fun hashCode(): Int { if (!ordered) return super.hashCode() val items = items var h = 1 var i = 0 val n = size while (i < n) { h *= 31 val item = items[i] if (item != null) h += item.hashCode() i++ } return h } /** * Returns false if either array is unordered. */ override fun equals(other: Any?): Boolean { if (other === this) return true if (!ordered) return false if (other !is ExpandingArray<*>) return false @Suppress("UNCHECKED_CAST") other as ExpandingArray if (!other.ordered) return false val n = size if (n != other.size) return false val items1 = items val items2= other.items for (i in 0 until n) { val o1 = items1[i] val o2 = items2[i] if (!(if (o1 == null) o2 == null else o1 == o2)) return false } return true } /** * Uses == for comparison of each item. Returns false if either array is unordered. */ fun equalsIdentity(other: Any): Boolean { if (other === this) return true if (!ordered) return false if (other !is ExpandingArray<*>) return false @Suppress("UNCHECKED_CAST") other as ExpandingArray if (!other.ordered) return false val n = size if (n != other.size) return false val items1 = items val items2 = other.items for (i in 0 until n) if (items1[i] !== items2[i]) return false return true } override fun toString(): String { if (size == 0) return "[]" val items = items val buffer = StringBuilder(32) buffer.append('[') buffer.append(items[0]) for (i in 1 until size) { buffer.append(", ") buffer.append(items[i]) } buffer.append(']') return buffer.toString() } fun toString(separator: String): String { if (size == 0) return "" val items = items val buffer = StringBuilder(32) buffer.append(items[0]) for (i in 1 until size) { buffer.append(separator) buffer.append(items[i]) } return buffer.toString() } class ArrayIterator(private val array: ExpandingArray, private val allowRemove: Boolean = true) : MutableIterator, Iterable { // ArrayIterable iterable; var index = 0 var valid = true override fun hasNext(): Boolean { if (!valid) { // System.out.println(iterable.lastAcquire); throw RuntimeException("#iterator() cannot be used nested.") } return index < array.size } override fun next(): T { if (index >= array.size) throw NoSuchElementException(index.toString()) if (!valid) { // System.out.println(iterable.lastAcquire); throw RuntimeException("#iterator() cannot be used nested.") } return array.items[index++]!! } override fun remove() { if (!allowRemove) throw RuntimeException("Remove not allowed.") index-- array.removeIndex(index) } fun reset() { index = 0 } override fun iterator(): ArrayIterator { return this } } class ArrayIterable(array: ExpandingArray, allowRemove: Boolean = true) : Iterable { private val array: ExpandingArray private val allowRemove: Boolean private var iterator1: ArrayIterator? = null private var iterator2: ArrayIterator? = null // java.io.StringWriter lastAcquire = new java.io.StringWriter(); init { this.array = array this.allowRemove = allowRemove } /** * @see Collections.allocateIterators */ override fun iterator(): ArrayIterator { if (allocateIterators) return ArrayIterator(array, allowRemove) // lastAcquire.getBuffer().setLength(0); // new Throwable().printStackTrace(new java.io.PrintWriter(lastAcquire)); if (iterator1 == null) { iterator1 = ArrayIterator(array, allowRemove) iterator2 = 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!! } } companion object { const val version = Collections.version /** * @see .Array */ fun of(arrayType: Class): ExpandingArray { return ExpandingArray(arrayType) } /** * @see .Array */ fun of(ordered: Boolean, capacity: Int, arrayType: Class): ExpandingArray { return ExpandingArray(ordered, capacity, arrayType) } /** * @see .Array */ fun with(vararg array: T): ExpandingArray { @Suppress("UNCHECKED_CAST") return ExpandingArray(array) as ExpandingArray } } }