From b6849efd47e19532d28352207ec3991007273ebc Mon Sep 17 00:00:00 2001 From: Abduqodiri Qurbonzoda Date: Wed, 22 Jan 2020 21:19:12 +0300 Subject: [PATCH] Common ArrayDeque --- .../src/kotlin/collections/ArrayDeque.kt | 561 +++++++++++++++ .../stdlib/test/collections/ArrayDequeTest.kt | 646 ++++++++++++++++++ .../kotlin-stdlib-runtime-merged.txt | 32 + 3 files changed, 1239 insertions(+) create mode 100644 libraries/stdlib/src/kotlin/collections/ArrayDeque.kt create mode 100644 libraries/stdlib/test/collections/ArrayDequeTest.kt diff --git a/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt new file mode 100644 index 00000000000..786320c6bc9 --- /dev/null +++ b/libraries/stdlib/src/kotlin/collections/ArrayDeque.kt @@ -0,0 +1,561 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package kotlin.collections + +private val emptyElementData = emptyArray() +private const val maxArraySize = Int.MAX_VALUE - 8 +private const val defaultMinCapacity = 10 + +/** + * Resizable-array implementation of the deque data structure. + * + * The name deque is short for "double ended queue" and is usually pronounced "deck". + * + * The collection provide methods for convenient access to the both ends. + * It also implements [MutableList] interface and supports efficient get/set operations by index. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public class ArrayDeque : AbstractMutableList { + private var head: Int = 0 + private var elementData: Array + + override var size: Int = 0 + private set + + /** + * Constructs an empty deque with specified [initialCapacity], or throws [IllegalArgumentException] if [initialCapacity] is negative. + */ + public constructor(initialCapacity: Int) { + elementData = when { + initialCapacity == 0 -> emptyElementData + initialCapacity > 0 -> arrayOfNulls(initialCapacity) + else -> throw IllegalArgumentException("Illegal Capacity: $initialCapacity") + } + } + + /** + * Constructs an empty deque. + */ + public constructor() { + elementData = emptyElementData + } + + /** + * Constructs a deque that contains the same elements as the specified [elements] collection in the same order. + */ + public constructor(elements: Collection) { + elementData = elements.toTypedArray() + size = elementData.size + if (elementData.isEmpty()) elementData = emptyElementData + } + + /** + * Ensures that the capacity of this deque is at least equal to the specified [minCapacity]. + * + * If the current capacity is less than the [minCapacity], a new backing storage is allocated with greater capacity. + * Otherwise, this method takes no action and simply returns. + */ + private fun ensureCapacity(minCapacity: Int) { + if (minCapacity < 0) throw IllegalStateException("Deque is too big.") // overflow + if (minCapacity <= elementData.size) return + if (elementData === emptyElementData) { + elementData = arrayOfNulls(minCapacity.coerceAtLeast(defaultMinCapacity)) + return + } + + val newCapacity = newCapacity(elementData.size, minCapacity) + copyElements(newCapacity) + } + + // made internal for testing + internal fun newCapacity(oldCapacity: Int, minCapacity: Int): Int { + // overflow-conscious + var newCapacity = oldCapacity + (oldCapacity shr 1) + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity + if (newCapacity - maxArraySize > 0) + newCapacity = if (minCapacity > maxArraySize) Int.MAX_VALUE else maxArraySize + return newCapacity + } + + /** + * Creates a new array with the specified [newCapacity] size and copies elements in the [elementData] array to it. + */ + private fun copyElements(newCapacity: Int) { + val newElements = arrayOfNulls(newCapacity) + elementData.copyInto(newElements, 0, head, elementData.size) + elementData.copyInto(newElements, elementData.size - head, 0, head) + head = 0 + elementData = newElements + } + + @kotlin.internal.InlineOnly + private inline fun internalGet(internalIndex: Int): E { + @Suppress("UNCHECKED_CAST") + return elementData[internalIndex] as E + } + + private fun positiveMod(index: Int): Int = if (index >= elementData.size) index - elementData.size else index + + private fun negativeMod(index: Int): Int = if (index < 0) index + elementData.size else index + + @kotlin.internal.InlineOnly + private inline fun internalIndex(index: Int): Int = positiveMod(head + index) + + private fun incremented(index: Int): Int = if (index == elementData.lastIndex) 0 else index + 1 + + private fun decremented(index: Int): Int = if (index == 0) elementData.lastIndex else index - 1 + + override fun isEmpty(): Boolean = size == 0 + + /** + * Returns the first element, or throws [NoSuchElementException] if this deque is empty. + */ + public fun first(): E = if (isEmpty()) throw NoSuchElementException("ArrayDeque is empty.") else internalGet(head) + + /** + * Returns the first element, or `null` if this deque is empty. + */ + public fun firstOrNull(): E? = if (isEmpty()) null else internalGet(head) + + /** + * Returns the last element, or throws [NoSuchElementException] if this deque is empty. + */ + public fun last(): E = if (isEmpty()) throw NoSuchElementException("ArrayDeque is empty.") else internalGet(internalIndex(lastIndex)) + + /** + * Returns the last element, or `null` if this deque is empty. + */ + public fun lastOrNull(): E? = if (isEmpty()) null else internalGet(internalIndex(lastIndex)) + + /** + * Prepends the specified [element] to this deque. + */ + public fun addFirst(element: E) { + ensureCapacity(size + 1) + + head = decremented(head) + elementData[head] = element + size += 1 + } + + /** + * Appends the specified [element] to this deque. + */ + public fun addLast(element: E) { + ensureCapacity(size + 1) + + elementData[internalIndex(size)] = element + size += 1 + } + + /** + * Removes the first element from this deque and returns that removed element, or throws [NoSuchElementException] if this deque is empty. + */ + public fun removeFirst(): E { + if (isEmpty()) throw NoSuchElementException("ArrayDeque is empty.") + + val element = internalGet(head) + elementData[head] = null + head = incremented(head) + size -= 1 + return element + } + + /** + * Removes the first element from this deque and returns that removed element, or returns `null` if this deque is empty. + */ + public fun removeFirstOrNull(): E? = if (isEmpty()) null else removeFirst() + + /** + * Removes the last element from this deque and returns that removed element, or throws [NoSuchElementException] if this deque is empty. + */ + public fun removeLast(): E { + if (isEmpty()) throw NoSuchElementException("ArrayDeque is empty.") + + val internalLastIndex = internalIndex(lastIndex) + val element = internalGet(internalLastIndex) + elementData[internalLastIndex] = null + size -= 1 + return element + } + + /** + * Removes the last element from this deque and returns that removed element, or returns `null` if this deque is empty. + */ + public fun removeLastOrNull(): E? = if (isEmpty()) null else removeLast() + + // MutableList, MutableCollection + public override fun add(element: E): Boolean { + addLast(element) + return true + } + + public override fun add(index: Int, element: E) { + AbstractList.checkPositionIndex(index, size) + + if (index == size) { + addLast(element) + return + } else if (index == 0) { + addFirst(element) + return + } + + ensureCapacity(size + 1) + + // Elements in circular array lay in 2 ways: + // 1. `head` is less than `tail`: [#, #, e1, e2, e3, #] + // 2. `head` is greater than `tail`: [e3, #, #, #, e1, e2] + // where head is the index of the first element in the circular array, + // and tail is the index following the last element. + // + // At this point the insertion index is not equal to head or tail. + // Also the circular array can store at least one more element. + // + // Depending on where the given element must be inserted the preceding or the succeeding + // elements will be shifted to make room for the element to be inserted. + // + // In case the preceding elements are shifted: + // * if the insertion index is greater than the head (regardless of circular array form) + // -> shift the preceding elements + // * otherwise, the circular array has (2) form and the insertion index is less than tail + // -> shift all elements in the back of the array + // -> shift preceding elements in the front of the array + // In case the succeeding elements are shifted: + // * if the insertion index is less than the tail (regardless of circular array form) + // -> shift the succeeding elements + // * otherwise, the circular array has (2) form and the insertion index is greater than head + // -> shift all elements in the front of the array + // -> shift succeeding elements in the back of the array + + val internalIndex = internalIndex(index) + + if (index < (size + 1) shr 1) { + // closer to the first element -> shift preceding elements + val decrementedInternalIndex = decremented(internalIndex) + val decrementedHead = decremented(head) + + if (decrementedInternalIndex >= head) { + elementData[decrementedHead] = elementData[head] // head can be zero + elementData.copyInto(elementData, head, head + 1, decrementedInternalIndex + 1) + } else { // head > tail + elementData.copyInto(elementData, head - 1, head, elementData.size) // head can't be zero + elementData[elementData.size - 1] = elementData[0] + elementData.copyInto(elementData, 0, 1, decrementedInternalIndex + 1) + } + + elementData[decrementedInternalIndex] = element + head = decrementedHead + } else { + // closer to the last element -> shift succeeding elements + val tail = internalIndex(size) + + if (internalIndex < tail) { + elementData.copyInto(elementData, internalIndex + 1, internalIndex, tail) + } else { // head > tail + elementData.copyInto(elementData, 1, 0, tail) + elementData[0] = elementData[elementData.size - 1] + elementData.copyInto(elementData, internalIndex + 1, internalIndex, elementData.size - 1) + } + + elementData[internalIndex] = element + } + size += 1 + } + + private fun copyCollectionElements(internalIndex: Int, elements: Collection) { + val iterator = elements.iterator() + + for (index in internalIndex until elementData.size) { + if (!iterator.hasNext()) break + elementData[index] = iterator.next() + } + for (index in 0 until head) { + if (!iterator.hasNext()) break + elementData[index] = iterator.next() + } + + size += elements.size + } + + public override fun addAll(elements: Collection): Boolean { + if (elements.isEmpty()) return false + ensureCapacity(this.size + elements.size) + copyCollectionElements(internalIndex(size), elements) + return true + } + + public override fun addAll(index: Int, elements: Collection): Boolean { + AbstractList.checkPositionIndex(index, size) + + if (elements.isEmpty()) { + return false + } else if (index == size) { + return addAll(elements) + } + + ensureCapacity(this.size + elements.size) + + val tail = internalIndex(size) + val internalIndex = internalIndex(index) + val elementsSize = elements.size + + if (index < (size + 1) shr 1) { + // closer to the first element -> shift preceding elements + + var shiftedHead = head - elementsSize + + if (internalIndex >= head) { + if (shiftedHead >= 0) { + elementData.copyInto(elementData, shiftedHead, head, internalIndex) + } else { // head < tail, insertion leads to head >= tail + shiftedHead += elementData.size + val elementsToShift = internalIndex - head + val shiftToBack = elementData.size - shiftedHead + + if (shiftToBack >= elementsToShift) { + elementData.copyInto(elementData, shiftedHead, head, internalIndex) + } else { + elementData.copyInto(elementData, shiftedHead, head, head + shiftToBack) + elementData.copyInto(elementData, 0, head + shiftToBack, internalIndex) + } + } + } else { // head > tail, internalIndex < tail + elementData.copyInto(elementData, shiftedHead, head, elementData.size) + if (elementsSize >= internalIndex) { + elementData.copyInto(elementData, elementData.size - elementsSize, 0, internalIndex) + } else { + elementData.copyInto(elementData, elementData.size - elementsSize, 0, elementsSize) + elementData.copyInto(elementData, 0, elementsSize, internalIndex) + } + } + head = shiftedHead + copyCollectionElements(negativeMod(internalIndex - elementsSize), elements) + } else { + // closer to the last element -> shift succeeding elements + + val shiftedInternalIndex = internalIndex + elementsSize + + if (internalIndex < tail) { + if (tail + elementsSize <= elementData.size) { + elementData.copyInto(elementData, shiftedInternalIndex, internalIndex, tail) + } else { // head < tail, insertion leads to head >= tail + if (shiftedInternalIndex >= elementData.size) { + elementData.copyInto(elementData, shiftedInternalIndex - elementData.size, internalIndex, tail) + } else { + val shiftToFront = tail + elementsSize - elementData.size + elementData.copyInto(elementData, 0, tail - shiftToFront, tail) + elementData.copyInto(elementData, shiftedInternalIndex, internalIndex, tail - shiftToFront) + } + } + } else { // head > tail, internalIndex > head + elementData.copyInto(elementData, elementsSize, 0, tail) + if (shiftedInternalIndex >= elementData.size) { + elementData.copyInto(elementData, shiftedInternalIndex - elementData.size, internalIndex, elementData.size) + } else { + elementData.copyInto(elementData, 0, elementData.size - elementsSize, elementData.size) + elementData.copyInto(elementData, shiftedInternalIndex, internalIndex, elementData.size - elementsSize) + } + } + copyCollectionElements(internalIndex, elements) + } + + return true + } + + public override fun get(index: Int): E { + AbstractList.checkElementIndex(index, size) + + return internalGet(internalIndex(index)) + } + + public override fun set(index: Int, element: E): E { + AbstractList.checkElementIndex(index, size) + + val internalIndex = internalIndex(index) + val oldElement = internalGet(internalIndex) + elementData[internalIndex] = element + + return oldElement + } + + public override fun contains(element: E): Boolean = indexOf(element) != -1 + + public override fun indexOf(element: E): Int { + val tail = internalIndex(size) + + if (head < tail) { + for (index in head until tail) { + if (element == elementData[index]) return index - head + } + } else if (head >= tail) { + for (index in head until elementData.size) { + if (element == elementData[index]) return index - head + } + for (index in 0 until tail) { + if (element == elementData[index]) return index + elementData.size - head + } + } + + return -1 + } + + public override fun lastIndexOf(element: E): Int { + val tail = internalIndex(size) + + if (head < tail) { + for (index in tail - 1 downTo head) { + if (element == elementData[index]) return index - head + } + } else if (head > tail) { + for (index in tail - 1 downTo 0) { + if (element == elementData[index]) return index + elementData.size - head + } + for (index in elementData.lastIndex downTo head) { + if (element == elementData[index]) return index - head + } + } + + return -1 + } + + public override fun remove(element: E): Boolean { + val index = indexOf(element) + if (index == -1) return false + removeAt(index) + return true + } + + public override fun removeAt(index: Int): E { + AbstractList.checkElementIndex(index, size) + + if (index == lastIndex) { + return removeLast() + } else if (index == 0) { + return removeFirst() + } + + val internalIndex = internalIndex(index) + val element = internalGet(internalIndex) + + if (index < size shr 1) { + // closer to the first element -> shift preceding elements + if (internalIndex >= head) { + elementData.copyInto(elementData, head + 1, head, internalIndex) + } else { // head > tail, internalIndex < head + elementData.copyInto(elementData, 1, 0, internalIndex) + elementData[0] = elementData[elementData.size - 1] + elementData.copyInto(elementData, head + 1, head, elementData.size - 1) + } + + elementData[head] = null + head = incremented(head) + } else { + // closer to the last element -> shift succeeding elements + val internalLastIndex = internalIndex(lastIndex) + + if (internalIndex <= internalLastIndex) { + elementData.copyInto(elementData, internalIndex, internalIndex + 1, internalLastIndex + 1) + } else { // head > tail, internalIndex > head + elementData.copyInto(elementData, internalIndex, internalIndex + 1, elementData.size) + elementData[elementData.size - 1] = elementData[0] + elementData.copyInto(elementData, 0, 1, internalLastIndex + 1) + } + + elementData[internalLastIndex] = null + } + size -= 1 + + return element + } + + public override fun removeAll(elements: Collection): Boolean = filterInPlace { !elements.contains(it) } + + public override fun retainAll(elements: Collection): Boolean = filterInPlace { elements.contains(it) } + + private inline fun filterInPlace(predicate: (E) -> Boolean): Boolean { + if (this.isEmpty() || elementData.isEmpty()) + return false + + val tail = internalIndex(size) + var newTail = head + var modified = false + + if (head < tail) { + for (index in head until tail) { + val element = elementData[index] + + @Suppress("UNCHECKED_CAST") + if (predicate(element as E)) + elementData[newTail++] = element + else + modified = true + } + + elementData.fill(null, newTail, tail) + + } else { + for (index in head until elementData.size) { + val element = elementData[index] + elementData[index] = null + + @Suppress("UNCHECKED_CAST") + if (predicate(element as E)) + elementData[newTail++] = element + else + modified = true + } + + newTail = positiveMod(newTail) + + for (index in 0 until tail) { + val element = elementData[index] + elementData[index] = null + + @Suppress("UNCHECKED_CAST") + if (predicate(element as E)) { + elementData[newTail] = element + newTail = incremented(newTail) + } else { + modified = true + } + } + } + if (modified) + size = negativeMod(newTail - head) + + return modified + } + + public override fun clear() { + val tail = internalIndex(size) + if (head < tail) { + elementData.fill(null, head, tail) + } else if (isNotEmpty()) { + elementData.fill(null, head, elementData.size) + elementData.fill(null, 0, tail) + } + head = 0 + size = 0 + } + + // For testing only + internal fun internalStructure(structure: (head: Int, elements: Array) -> Unit) { + val tail = internalIndex(size) + + if (isEmpty()) { + structure(head, emptyArray()) + } else if (head < tail) { + @Suppress("UNCHECKED_CAST") + structure(head, elementData.sliceArray(head until tail)) + } else { + @Suppress("UNCHECKED_CAST") + structure(head - elementData.size, elementData.sliceArray(head until elementData.size).plus(elements = elementData.sliceArray(0 until tail))) + } + } +} \ No newline at end of file diff --git a/libraries/stdlib/test/collections/ArrayDequeTest.kt b/libraries/stdlib/test/collections/ArrayDequeTest.kt new file mode 100644 index 00000000000..4a67147b823 --- /dev/null +++ b/libraries/stdlib/test/collections/ArrayDequeTest.kt @@ -0,0 +1,646 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package collections + +import test.collections.behaviors.iteratorBehavior +import test.collections.behaviors.listIteratorBehavior +import test.collections.behaviors.listIteratorProperties +import test.collections.compare +import kotlin.random.Random +import kotlin.random.nextInt +import kotlin.test.* + +class ArrayDequeTest { + @Test + fun arrayDequeInit() { + listOf(ArrayDeque(), ArrayDeque(10)).forEach { deque -> + assertTrue(deque.isEmpty()) + assertNull(deque.firstOrNull()) + assertNull(deque.lastOrNull()) + assertNull(deque.removeFirstOrNull()) + assertNull(deque.removeLastOrNull()) + } + + ArrayDeque(listOf(0, 1, 2, 3, 4)).let { deque -> + assertFalse(deque.isEmpty()) + assertEquals(0, deque.firstOrNull()) + assertEquals(4, deque.lastOrNull()) + assertEquals(0, deque.removeFirstOrNull()) + assertEquals(4, deque.removeLastOrNull()) + } + } + + @Test + fun size() { + listOf(ArrayDeque(), ArrayDeque(10)).forEach { deque -> + // head == tail + assertEquals(0, deque.size) + // head > tail + deque.addFirst(-1) + assertEquals(1, deque.size) + // buffer expansion, head < tail + deque.addAll(listOf(1, 2, 3, 4, 5, 6, 7)) + assertEquals(8, deque.size) + } + + ArrayDeque(listOf(0, 1, 2, 3, 4)).let { deque -> + // head < tail + assertEquals(5, deque.size) + // head > tail + deque.addFirst(-1) + assertEquals(6, deque.size) + // buffer expansion, head < tail + deque.addAll(listOf(5, 6, 7)) + assertEquals(9, deque.size) + } + } + + @Test + fun contains() { + val originalItems = listOf(0, 1, 2, 3, 4) + val extraItems = listOf(-1, 5) + val deque = ArrayDeque(originalItems) + // head < tail + originalItems.forEach { assertTrue(deque.contains(it), "Expected $it in $deque") } + extraItems.forEach { assertFalse(deque.contains(it), "Not expected $it in $deque") } + + // head > tail + deque.addFirst(-1) + deque.addLast(5) + + (originalItems + extraItems).forEach { assertTrue(deque.contains(it), "Expected $it in $deque") } + + // remove, head > tail + deque.remove(2) + + assertTrue(deque.contains(1)) + assertFalse(deque.contains(2)) + assertTrue(deque.contains(3)) + + // remove, head < tail + deque.remove(-1) + + assertTrue(deque.contains(5)) + assertFalse(deque.contains(-1)) + assertTrue(deque.contains(0)) + } + + @Test + fun clear() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + val deque = generateArrayDeque(head, tail, bufferSize).apply { clear() } + assertTrue(deque.isEmpty()) + } + + @Test + fun removeElement() { + val deque = ArrayDeque() + deque.addLast(0) + deque.addLast(1) + deque.addLast(2) + deque.addLast(3) + assertTrue(deque.contains(2)) + deque.remove(2) + assertFalse(deque.contains(2)) + + deque.addFirst(-1) + assertTrue(deque.containsAll(listOf(-1, 0, 1, 3))) + deque.remove(-1) + assertTrue(deque.containsAll(listOf(0, 1, 3))) + deque.remove(0) + assertTrue(deque.containsAll(listOf(1, 3))) + } + + @Test + fun iterator() { + val deque = ArrayDeque() + + deque.addLast(0) + deque.addLast(1) + deque.addLast(2) + deque.addLast(3) + compare(deque.iterator(), listOf(0, 1, 2, 3).iterator()) { iteratorBehavior() } + + deque.addFirst(-1) + compare(deque.iterator(), listOf(-1, 0, 1, 2, 3).iterator()) { iteratorBehavior() } + } + + @Test + fun first() { + val deque = ArrayDeque() + assertFailsWith { deque.first() } + + deque.addLast(0) + deque.addLast(1) + assertEquals(0, deque.first()) + + deque.addFirst(-1) + assertEquals(-1, deque.first()) + + deque.removeFirst() + assertEquals(0, deque.first()) + + deque.clear() + assertFailsWith { deque.first() } + } + + @Test + fun firstOrNull() { + val deque = ArrayDeque() + assertNull(deque.firstOrNull()) + + deque.addLast(0) + deque.addLast(1) + assertEquals(0, deque.firstOrNull()) + + deque.addFirst(-1) + assertEquals(-1, deque.firstOrNull()) + + deque.removeFirst() + assertEquals(0, deque.firstOrNull()) + + deque.clear() + assertNull(deque.firstOrNull()) + } + + @Test + fun last() { + val deque = ArrayDeque() + assertFailsWith { deque.last() } + + deque.addLast(0) + deque.addLast(1) + assertEquals(1, deque.last()) + + deque.addFirst(-1) + assertEquals(1, deque.last()) + + deque.removeLast() + assertEquals(0, deque.last()) + + deque.clear() + assertFailsWith { deque.last() } + } + + @Test + fun lastOrNull() { + val deque = ArrayDeque() + assertNull(deque.lastOrNull()) + + deque.addLast(0) + deque.addLast(1) + assertEquals(1, deque.lastOrNull()) + + deque.addFirst(-1) + assertEquals(1, deque.lastOrNull()) + + deque.removeLast() + assertEquals(0, deque.lastOrNull()) + + deque.clear() + assertNull(deque.lastOrNull()) + } + + @Test + fun addFirst() { + val deque = ArrayDeque() + + // head > tail + listOf(-1, -2, -3).forEach { + deque.addFirst(it) + assertEquals(it, deque.first()) + } + + // head < tail + listOf(-1, -2).forEach { + assertEquals(it, deque.removeLast()) + } + + listOf(-4, -5, -6, -7, -8, -9).forEach { + deque.addFirst(it) + assertEquals(it, deque.first()) + } + + // buffer expansion, head < tail + deque.addFirst(-10) + assertEquals(-10, deque.first()) + } + + @Test + fun addLast() { + val deque = ArrayDeque() + + // head < tail + listOf(0, 1, 2).forEach { + deque.addLast(it) + assertEquals(it, deque.last()) + } + + // head > tail + listOf(0, 1).forEach { + assertEquals(it, deque.removeFirst()) + } + listOf(3, 4, 5, 6, 7, 8).forEach { + deque.addLast(it) + assertEquals(it, deque.last()) + } + + // buffer expansion, head < tail + deque.addLast(9) + assertEquals(9, deque.last()) + } + + @Test + fun removeFirst() { + val deque = ArrayDeque() + assertFailsWith { deque.removeFirst() } + + deque.addLast(0) + deque.addFirst(-1) + deque.addFirst(-2) + deque.addLast(1) + + assertEquals(-2, deque.removeFirst()) + assertEquals(-1, deque.removeFirst()) + assertEquals(0, deque.removeFirst()) + assertEquals(1, deque.removeFirst()) + + assertFailsWith { deque.removeFirst() } + } + + @Test + fun removeFirstOrNull() { + val deque = ArrayDeque() + assertNull(deque.removeFirstOrNull()) + + deque.addLast(0) + deque.addFirst(-1) + deque.addFirst(-2) + deque.addLast(1) + + assertEquals(-2, deque.removeFirstOrNull()) + assertEquals(-1, deque.removeFirstOrNull()) + assertEquals(0, deque.removeFirstOrNull()) + assertEquals(1, deque.removeFirstOrNull()) + + assertNull(deque.removeFirstOrNull()) + } + + @Test + fun removeLast() { + val deque = ArrayDeque() + assertFailsWith { deque.removeLast() } + + deque.addLast(0) + deque.addFirst(-1) + deque.addFirst(-2) + deque.addLast(1) + + assertEquals(1, deque.removeLast()) + assertEquals(0, deque.removeLast()) + assertEquals(-1, deque.removeLast()) + assertEquals(-2, deque.removeLast()) + + assertFailsWith { deque.removeLast() } + } + + @Test + fun removeLastOrNull() { + val deque = ArrayDeque() + assertNull(deque.removeLastOrNull()) + + deque.addLast(0) + deque.addFirst(-1) + deque.addFirst(-2) + deque.addLast(1) + + assertEquals(1, deque.removeLastOrNull()) + assertEquals(0, deque.removeLastOrNull()) + assertEquals(-1, deque.removeLastOrNull()) + assertEquals(-2, deque.removeLastOrNull()) + + assertNull(deque.removeLastOrNull()) + } + + @Test + fun bufferExpansion() { + val deque = ArrayDeque() + + deque.addAll(listOf(0, 1, 2, 3, 4, 5, 6, 7)) + assertEquals(listOf(0, 1, 2, 3, 4, 5, 6, 7), deque.toList()) + + listOf(-1, -2, -3, -4, -5, -6, -7, -8).forEach { deque.addFirst(it) } + assertEquals(listOf(-8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7), deque.toList()) + } + + private fun generateArrayDeque(head: Int, tail: Int, bufferSize: Int? = null): ArrayDeque { + check(tail >= 0) + + val deque = if (bufferSize != null) ArrayDeque(bufferSize) else ArrayDeque() + + repeat(tail) { + deque.addLast(it) + if (it < head) deque.removeFirst() + } + repeat(-head) { deque.addFirst(-(it + 1)) } + + check(tail - head == deque.size) + + return deque + } + + private fun testArrayDeque(test: (bufferSize: Int, dequeSize: Int, head: Int, tail: Int) -> Unit) { + for (bufferSize in listOf(0, 1, 2, 5, 8, 16)) { + for (dequeSize in 0..bufferSize) { + for (tail in 0 until bufferSize) { + val head = tail - dequeSize + test(bufferSize, dequeSize, head, tail) + } + } + } + } + + // MutableList operations + @Test + fun insert() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + for (index in 0..dequeSize) { + val deque = generateArrayDeque(head, tail, bufferSize).apply { add(index, 100) } + + val expectedHead = when { + dequeSize == bufferSize -> // buffer expansion + when { + index * 2 < dequeSize -> -1 // shift preceding elements + // shift succeeding elements + dequeSize + 1 >= bufferSize + bufferSize / 2 -> -(dequeSize + 1) // tail == 0 -> head becomes negative (head > tail) + else -> 0 + } + index * 2 < dequeSize -> head - 1 // shift preceding elements + else -> // shift succeeding last elements + if (tail == bufferSize - 1) + head - bufferSize // tail == 0 -> head becomes negative (head > tail) + else + head + } + val expectedElements = (head until tail).toMutableList().apply { add(index, 100) } + + @Suppress("INVISIBLE_MEMBER") + deque.internalStructure { actualHead, actualElements -> + assertEquals(expectedHead, actualHead, "bufferSize: $bufferSize, head: $head, tail: $tail, index: $index") + assertEquals(expectedElements, actualElements.toList()) + } + } + } + + @Test + fun removeAt() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + for (index in 0 until dequeSize) { + val deque = generateArrayDeque(head, tail, bufferSize).apply { removeAt(index) } + + val expectedHead = when { + index < dequeSize / 2 -> head + 1 // shift preceding elements + else -> // shift succeeding elements + if (tail == 0) + head + bufferSize // tail == bufferSize - 1 -> head becomes positive(head < tail) + else + head + } + val expectedElements = (head until tail).toMutableList().apply { removeAt(index) } + + @Suppress("INVISIBLE_MEMBER") + deque.internalStructure { actualHead, actualElements -> + assertEquals(expectedHead, actualHead, "bufferSize: $bufferSize, head: $head, tail: $tail, index: $index") + assertEquals(expectedElements, actualElements.toList()) + } + } + } + + @Test + fun indexOf() { + // head < tail + generateArrayDeque(0, 7).let { deque -> + (0..6).forEach { assertEquals(it, deque.indexOf(it)) } + assertEquals(-1, deque.indexOf(100)) + } + + // head > tail + generateArrayDeque(-4, 3).let { deque -> + (0..6).forEach { assertEquals(it, deque.indexOf(it - 4)) } + assertEquals(-1, deque.indexOf(100)) + } + } + + @Test + fun addAll() { + // head < tail + generateArrayDeque(0, 3).let { deque -> + deque.addAll(listOf(3, 4, 5)) + assertEquals(listOf(0, 1, 2, 3, 4, 5), deque.toList()) + + deque.addAll(6..100) + assertEquals((0..100).toList(), deque.toList()) + } + + generateArrayDeque(4, 6).let { deque -> + deque.addAll(listOf(6, 7, 8)) + assertEquals(listOf(4, 5, 6, 7, 8), deque.toList()) + + deque.addAll(9..100) + assertEquals((4..100).toList(), deque.toList()) + } + + // head > tail + generateArrayDeque(-3, 2).let { deque -> + deque.addAll(listOf(2, 3)) + assertEquals(listOf(-3, -2, -1, 0, 1, 2, 3), deque.toList()) + + deque.addAll(4..100) + assertEquals((-3..100).toList(), deque.toList()) + } + } + + @Test + fun insertAll() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + for (insertCollectionSize in 0..bufferSize) { + val listToInsert = (0 until insertCollectionSize).map { 100 + it } + + for (index in 0..dequeSize) { + val deque = generateArrayDeque(head, tail, bufferSize).apply { addAll(index, listToInsert) } + + val expectedHead = when { + dequeSize + listToInsert.size > bufferSize -> // buffer expansion + when { + index * 2 < dequeSize -> -listToInsert.size // shift preceding elements + // shift succeeding elements + dequeSize + listToInsert.size >= bufferSize + bufferSize / 2 -> -(dequeSize + listToInsert.size) // tail == 0 -> head becomes negative (head > tail) + else -> 0 + } + index * 2 < dequeSize -> head - listToInsert.size // shift preceding elements + else -> // shift succeeding elements + if (tail + listToInsert.size >= bufferSize) + head - bufferSize // tail >= 0 -> head becomes negative (head > tail) + else + head + } + val expectedElements = (head until tail).toMutableList().apply { addAll(index, listToInsert) } + + @Suppress("INVISIBLE_MEMBER") + deque.internalStructure { actualHead, actualElements -> + assertEquals(expectedHead, actualHead, "bufferSize: $bufferSize, head: $head, tail: $tail, index: $index") + assertEquals(expectedElements, actualElements.toList()) + } + } + } + } + + @Test + fun listIterator() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + + for (index in 0..dequeSize) { + val expectedIterator = (head until tail).toMutableList().listIterator(index) + val actualIterator = generateArrayDeque(head, tail, bufferSize).listIterator(index) + + compare(expectedIterator, actualIterator) { listIteratorBehavior() } + } + + for (index in 0..dequeSize) { + val expectedIterator = (head until tail).toMutableList().listIterator(index) + val actualIterator = generateArrayDeque(head, tail, bufferSize).listIterator(index) + + for (element in listOf(100, 101)) { + expectedIterator.add(element) + actualIterator.add(element) + compare(expectedIterator, actualIterator) { listIteratorProperties() } + } + } + + for (index in 0..dequeSize) { + val expectedIterator = (head until tail).toMutableList().listIterator(index) + val actualIterator = generateArrayDeque(head, tail, bufferSize).listIterator(index) + + repeat(times = 2) { + if (expectedIterator.hasNext()) { + expectedIterator.next() + actualIterator.next() + } else if (expectedIterator.hasPrevious()) { + expectedIterator.previous() + actualIterator.previous() + } + if (dequeSize > it) { + expectedIterator.remove() + actualIterator.remove() + } + compare(expectedIterator, actualIterator) { listIteratorProperties() } + } + } + } + + @Test + fun removeAll() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + generateArrayDeque(head, tail, bufferSize).let { deque -> + deque.removeAll(emptyList()) + assertEquals((head until tail).toList(), deque) + + val absentElements = listOf(head - 1, tail, Random.nextInt(tail..Int.MAX_VALUE)) + deque.removeAll(absentElements) + assertEquals((head until tail).toList(), deque) + + deque.removeAll((head until tail).toList()) + assertTrue(deque.isEmpty()) + } + + val listToRemove = (head - 1 until tail + 1).filter { Random.nextBoolean() } + + val elements = (head until tail).toMutableList().apply { removeAll(listToRemove) } + val deque = generateArrayDeque(head, tail, bufferSize).apply { removeAll(listToRemove) } + + assertEquals(elements, deque) + } + + @Test + fun retainAll() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + val listToRetain = (head..tail).filter { Random.nextBoolean() } + + val elements = (head until tail).toMutableList().apply { retainAll(listToRetain) } + val deque = generateArrayDeque(head, tail, bufferSize).apply { retainAll(listToRetain) } + + assertEquals(elements, deque) + } + + @Test + fun set() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + for (index in 0 until dequeSize) { + val elements = (head until tail).toMutableList() + val deque = generateArrayDeque(head, tail, bufferSize) + + elements[index] = 100 + deque[index] = 100 + + assertEquals(elements, deque) + } + } + + @Test + fun get() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + val elements = (head until tail).toList() + val deque = generateArrayDeque(head, tail, bufferSize) + + for (index in 0 until dequeSize) { + assertEquals(elements[index], deque[index]) + } + } + + @Test + fun subList() = testArrayDeque { bufferSize: Int, dequeSize: Int, head: Int, tail: Int -> + val elements = (head until tail).toList() + val deque = generateArrayDeque(head, tail, bufferSize) + + for (fromIndex in 0 until dequeSize) { + for (toIndex in fromIndex..dequeSize) { + assertEquals(elements.subList(fromIndex, toIndex), deque.subList(fromIndex, toIndex)) + } + } + } + + @Suppress("INVISIBLE_MEMBER") + @Test + fun newCapacity() { + val deque = ArrayDeque() + + // oldCapacity < minCapacity < newCapacity + repeat(100) { + val oldCapacity = Random.nextInt(1 shl 30) + val newCapacity = oldCapacity + (oldCapacity shr 1) + val minCapacity = Random.nextInt(oldCapacity + 1 until newCapacity) + + assertEquals(newCapacity, deque.newCapacity(oldCapacity, minCapacity)) + } + + // oldCapacity < newCapacity < minCapacity + repeat(100) { + val oldCapacity = Random.nextInt(1 shl 30) + val newCapacity = oldCapacity + (oldCapacity shr 1) + val minCapacity = Random.nextInt(newCapacity..Int.MAX_VALUE) + + assertEquals(minCapacity, deque.newCapacity(oldCapacity, minCapacity)) + } + + // newCapacity overflow, oldCapacity < minCapacity <= maxArraySize + val maxArraySize = Int.MAX_VALUE - 8 + repeat(100) { + val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29) until maxArraySize) + val minCapacity = Random.nextInt(oldCapacity..maxArraySize) + + assertEquals(maxArraySize, deque.newCapacity(oldCapacity, minCapacity)) + } + + // newCapacity overflow, minCapacity > maxArraySize + repeat(100) { + val oldCapacity = Random.nextInt((1 shl 30) + (1 shl 29)..maxArraySize) + val minCapacity = Random.nextInt(maxArraySize + 1..Int.MAX_VALUE) + + assertEquals(Int.MAX_VALUE, deque.newCapacity(oldCapacity, minCapacity)) + } + } +} \ No newline at end of file diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 5957385c343..1c014b2bf66 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -664,6 +664,38 @@ public abstract class kotlin/collections/AbstractSet : kotlin/collections/Abstra public fun iterator ()Ljava/util/Iterator; } +public final class kotlin/collections/ArrayDeque : kotlin/collections/AbstractMutableList { + public fun ()V + public fun (I)V + public fun (Ljava/util/Collection;)V + public fun add (ILjava/lang/Object;)V + public fun add (Ljava/lang/Object;)Z + public fun addAll (ILjava/util/Collection;)Z + public fun addAll (Ljava/util/Collection;)Z + public final fun addFirst (Ljava/lang/Object;)V + public final fun addLast (Ljava/lang/Object;)V + public fun clear ()V + public fun contains (Ljava/lang/Object;)Z + public final fun first ()Ljava/lang/Object; + public final fun firstOrNull ()Ljava/lang/Object; + public fun get (I)Ljava/lang/Object; + public fun getSize ()I + public fun indexOf (Ljava/lang/Object;)I + public fun isEmpty ()Z + public final fun last ()Ljava/lang/Object; + public fun lastIndexOf (Ljava/lang/Object;)I + public final fun lastOrNull ()Ljava/lang/Object; + public fun remove (Ljava/lang/Object;)Z + public fun removeAll (Ljava/util/Collection;)Z + public fun removeAt (I)Ljava/lang/Object; + public final fun removeFirst ()Ljava/lang/Object; + public final fun removeFirstOrNull ()Ljava/lang/Object; + public final fun removeLast ()Ljava/lang/Object; + public final fun removeLastOrNull ()Ljava/lang/Object; + public fun retainAll (Ljava/util/Collection;)Z + public fun set (ILjava/lang/Object;)Ljava/lang/Object; +} + public final class kotlin/collections/ArraysKt { public static final fun all ([BLkotlin/jvm/functions/Function1;)Z public static final fun all ([CLkotlin/jvm/functions/Function1;)Z