Optimized toArray method for ArrayDeque and ListBuilder #KT-42720

This commit is contained in:
Abduqodiri Qurbonzoda
2021-01-22 13:12:50 +03:00
parent c2f6a2cb96
commit 421cb6971d
8 changed files with 194 additions and 23 deletions
@@ -10519,6 +10519,10 @@ public final class ArrayDeque<E> : kotlin.collections.AbstractMutableList<E> {
public open override fun retainAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
public open override operator fun set(index: kotlin.Int, element: E): E
protected open override fun toArray(): kotlin.Array<kotlin.Any?>
protected open override fun <T> toArray(array: kotlin.Array<T>): kotlin.Array<T>
}
public open class ArrayList<E> : kotlin.collections.AbstractMutableList<E>, kotlin.collections.MutableList<E>, kotlin.collections.RandomAccess {
@@ -10558,6 +10562,8 @@ public open class ArrayList<E> : kotlin.collections.AbstractMutableList<E>, kotl
protected open override fun toArray(): kotlin.Array<kotlin.Any?>
protected open override fun <T> toArray(array: kotlin.Array<T>): kotlin.Array<T>
public open override fun toString(): kotlin.String
public final fun trimToSize(): kotlin.Unit
@@ -10483,6 +10483,10 @@ public final class ArrayDeque<E> : kotlin.collections.AbstractMutableList<E> {
public open override fun retainAll(elements: kotlin.collections.Collection<E>): kotlin.Boolean
public open override operator fun set(index: kotlin.Int, element: E): E
protected open override fun toArray(): kotlin.Array<kotlin.Any?>
protected open override fun <T> toArray(array: kotlin.Array<T>): kotlin.Array<T>
}
public open class ArrayList<E> : kotlin.collections.AbstractMutableList<E>, kotlin.collections.MutableList<E>, kotlin.collections.RandomAccess {
@@ -10522,6 +10526,8 @@ public open class ArrayList<E> : kotlin.collections.AbstractMutableList<E>, kotl
protected open override fun toArray(): kotlin.Array<kotlin.Any?>
protected open override fun <T> toArray(array: kotlin.Array<T>): kotlin.Array<T>
public open override fun toString(): kotlin.String
public final fun trimToSize(): kotlin.Unit
@@ -133,7 +133,25 @@ public actual open class ArrayList<E> internal constructor(private var array: Ar
actual override fun lastIndexOf(element: E): Int = array.lastIndexOf(element)
override fun toString() = arrayToString(array)
override fun toArray(): Array<Any?> = js("[]").slice.call(array)
@Suppress("UNCHECKED_CAST")
override fun <T> toArray(array: Array<T>): Array<T> {
if (array.size < size) {
return toArray() as Array<T>
}
(this.array as Array<T>).copyInto(array)
if (array.size > size) {
array[size] = null as T // null-terminate
}
return array
}
override fun toArray(): Array<Any?> {
return js("[]").slice.call(array)
}
internal override fun checkIsMutable() {
@@ -130,13 +130,25 @@ internal class ListBuilder<E> private constructor(
return ListBuilder(array, offset + fromIndex, toIndex - fromIndex, isReadOnly, this, root ?: this)
}
@OptIn(ExperimentalStdlibApi::class)
private fun ensureCapacity(minCapacity: Int) {
if (backing != null) throw IllegalStateException() // just in case somebody casts subList to ListBuilder
if (minCapacity > array.size) {
val newSize = ArrayDeque.newCapacity(array.size, minCapacity)
array = array.copyOfUninitializedElements(newSize)
override fun <T> toArray(destination: Array<T>): Array<T> {
if (destination.size < length) {
return java.util.Arrays.copyOfRange(array, offset, offset + length, destination.javaClass)
}
@Suppress("UNCHECKED_CAST")
(array as Array<T>).copyInto(destination, 0, startIndex = offset, endIndex = offset + length)
if (destination.size > length) {
@Suppress("UNCHECKED_CAST")
destination[length] = null as T // null-terminate
}
return destination
}
override fun toArray(): Array<Any?> {
@Suppress("UNCHECKED_CAST")
return array.copyOfRange(fromIndex = offset, toIndex = offset + length) as Array<Any?>
}
override fun equals(other: Any?): Boolean {
@@ -154,6 +166,14 @@ internal class ListBuilder<E> private constructor(
// ---------------------------- private ----------------------------
private fun ensureCapacity(minCapacity: Int) {
if (backing != null) throw IllegalStateException() // just in case somebody casts subList to ListBuilder
if (minCapacity > array.size) {
val newSize = ArrayDeque.newCapacity(array.size, minCapacity)
array = array.copyOfUninitializedElements(newSize)
}
}
private fun checkIsMutable() {
if (isReadOnly || root != null && root.isReadOnly) throw UnsupportedOperationException()
}
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2021 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 test.collections
import kotlin.collections.builders.*
import kotlin.test.Test
import kotlin.test.assertSame
import kotlin.test.assertTrue
@Suppress("INVISIBLE_MEMBER")
class ListBuilderTest {
@Test
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
fun toArray() {
val numberOfElements = 5
val expected = ArrayList<Int>().apply { addAll(0 until numberOfElements) }
val builder = ListBuilder<Int>().apply { addAll(0 until numberOfElements) }
fun testToArray(getter: List<Int>.() -> Array<Any?>) {
assertTrue(expected.getter() contentEquals builder.getter())
assertTrue(expected.subList(2, 4).getter() contentEquals builder.subList(2, 4).getter())
}
testToArray { (this as java.util.Collection<Int>).toArray() }
}
@Test
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST")
fun toArrayDestination() {
val numberOfElements = 5
val expected = ArrayList<Int>().apply { addAll(0 until numberOfElements) }
val builder = ListBuilder<Int>().apply { addAll(0 until numberOfElements) }
fun testToArray(destSize: Int, getter: List<Int>.(Array<Int>) -> Array<Int>) {
repeat(2) { index ->
val expectedDest = Array(destSize) { -it - 1 }
val builderDest = Array(destSize) { -it - 1 }
val takeSubList = index == 1
val expectedResult = (if (!takeSubList) expected else expected.subList(2, 4)).getter(expectedDest)
val builderResult = (if (!takeSubList) builder else builder.subList(2, 4)).getter(builderDest)
if (expectedResult.size <= expectedDest.size) {
assertSame(expectedDest, expectedResult)
assertSame(builderDest, builderResult)
}
assertTrue(expectedResult contentEquals builderResult)
}
}
testToArray(0) { (this as java.util.Collection<Int>).toArray(it) }
testToArray(numberOfElements - 1) { (this as java.util.Collection<Int>).toArray(it) }
testToArray(numberOfElements) { (this as java.util.Collection<Int>).toArray(it) }
testToArray(numberOfElements + 1) { (this as java.util.Collection<Int>).toArray(it) }
testToArray(numberOfElements + 2) { (this as java.util.Collection<Int>).toArray(it) }
}
}
@@ -529,6 +529,35 @@ public class ArrayDeque<E> : AbstractMutableList<E> {
size = 0
}
@Suppress("NOTHING_TO_OVERRIDE")
override fun <T> toArray(array: Array<T>): Array<T> {
@Suppress("UNCHECKED_CAST")
val dest = (if (array.size >= size) array else arrayOfNulls(array, size)) as Array<Any?>
val tail = internalIndex(size)
if (head < tail) {
elementData.copyInto(dest, startIndex = head, endIndex = tail)
} else if (isNotEmpty()) {
elementData.copyInto(dest, destinationOffset = 0, startIndex = head, endIndex = elementData.size)
elementData.copyInto(dest, destinationOffset = elementData.size - head, startIndex = 0, endIndex = tail)
}
if (dest.size > size) {
dest[size] = null // null-terminate
}
@Suppress("UNCHECKED_CAST")
return dest as Array<T>
}
@Suppress("NOTHING_TO_OVERRIDE")
override fun toArray(): Array<Any?> {
return toArray(arrayOfNulls<Any?>(size))
}
// for testing
internal fun <T> testToArray(array: Array<T>): Array<T> = toArray(array)
internal fun testToArray(): Array<Any?> = toArray()
internal companion object {
private val emptyElementData = emptyArray<Any?>()
private const val maxArraySize = Int.MAX_VALUE - 8
@@ -548,20 +577,7 @@ public class ArrayDeque<E> : AbstractMutableList<E> {
// For testing only
internal fun internalStructure(structure: (head: Int, elements: Array<Any?>) -> Unit) {
val tail = internalIndex(size)
if (isEmpty()) {
structure(head, emptyArray())
return
}
val elements = arrayOfNulls<Any?>(size)
if (head < tail) {
elementData.copyInto(elements, startIndex = head, endIndex = tail)
structure(head, elements)
} else {
elementData.copyInto(elements, startIndex = head)
elementData.copyInto(elements, elementData.size - head, startIndex = 0, endIndex = tail)
structure(head - elementData.size, elements)
}
val head = if (isEmpty() || head < tail) head else head - elementData.size
structure(head, toArray())
}
}
@@ -8,7 +8,6 @@ package test.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.*
@@ -647,4 +646,48 @@ class ArrayDequeTest {
assertEquals(Int.MAX_VALUE, ArrayDeque.newCapacity(oldCapacity, minCapacity))
}
}
@Suppress("INVISIBLE_MEMBER")
@Test
fun toArray() {
val deque = ArrayDeque<Int>()
// empty
assertTrue(deque.testToArray().isEmpty())
fun testContentEquals(expected: Array<Int>) {
assertTrue(expected contentEquals deque.testToArray())
assertTrue(expected contentEquals deque.testToArray(emptyArray()))
val dest = Array(expected.size + 2) { it + 100 }
@Suppress("UNCHECKED_CAST")
val nullTerminatedExpected = (expected as Array<Any?>) + null + (expected.size + 101)
val actual = deque.testToArray(dest)
assertTrue(
nullTerminatedExpected contentEquals actual,
message = "Expected: ${nullTerminatedExpected.contentToString()}, Actual: ${actual.contentToString()}"
)
}
// head < tail
deque.addAll(listOf(0, 1, 2, 3))
deque.internalStructure { head, _ -> assertEquals(0, head) }
testContentEquals(arrayOf(0, 1, 2, 3))
deque.removeFirst()
deque.internalStructure { head, _ -> assertEquals(1, head) }
testContentEquals(arrayOf(1, 2, 3))
// head > tail
deque.addFirst(-1)
deque.addFirst(-2)
deque.addFirst(-3)
deque.internalStructure { head, _ -> assertEquals(-2, head) } // deque min capacity is 10
testContentEquals(arrayOf(-3, -2, -1, 1, 2, 3))
// head == tail
deque.addAll(listOf(4, 5, 6, 7))
deque.internalStructure { head, _ -> assertEquals(-2, head) } // deque min capacity is 10
testContentEquals(arrayOf(-3, -2, -1, 1, 2, 3, 4, 5, 6, 7))
}
}