Specialize contentDeepEquals/HashCode/ToString for arrays of unsigned types

#KT-26388
This commit is contained in:
Ilya Gorbunov
2018-09-12 02:41:27 +03:00
parent 3cc606577c
commit 2d356b89b5
8 changed files with 208 additions and 47 deletions
+4 -38
View File
@@ -48,20 +48,8 @@ Kotlin.arrayToString = function (a) {
return "[" + Array.prototype.map.call(a, function(e) { return toString(e); }).join(", ") + "]";
};
Kotlin.arrayDeepToString = function (a, visited) {
visited = visited || [a];
var toString = Kotlin.isCharArray(a) ? String.fromCharCode : Kotlin.toString;
return "[" + Array.prototype.map.call(a, function (e) {
if (Kotlin.isArrayish(e) && visited.indexOf(e) < 0) {
visited.push(e);
var result = Kotlin.arrayDeepToString(e, visited);
visited.pop();
return result;
}
else {
return toString(e);
}
}).join(", ") + "]";
Kotlin.arrayDeepToString = function (arr) {
return Kotlin.kotlin.collections.contentDeepToStringImpl(arr);
};
Kotlin.arrayEquals = function (a, b) {
@@ -81,24 +69,7 @@ Kotlin.arrayEquals = function (a, b) {
};
Kotlin.arrayDeepEquals = function (a, b) {
if (a === b) {
return true;
}
if (!Kotlin.isArrayish(b) || a.length !== b.length) {
return false;
}
for (var i = 0, n = a.length; i < n; i++) {
if (Kotlin.isArrayish(a[i])) {
if (!Kotlin.arrayDeepEquals(a[i], b[i])) {
return false;
}
}
else if (!Kotlin.equals(a[i], b[i])) {
return false;
}
}
return true;
return Kotlin.kotlin.collections.contentDeepEqualsImpl(a, b);
};
Kotlin.arrayHashCode = function (arr) {
@@ -110,12 +81,7 @@ Kotlin.arrayHashCode = function (arr) {
};
Kotlin.arrayDeepHashCode = function (arr) {
var result = 1;
for (var i = 0, n = arr.length; i < n; i++) {
var e = arr[i];
result = ((31 * result | 0) + (Kotlin.isArrayish(e) ? Kotlin.arrayDeepHashCode(e) : Kotlin.hashCode(e))) | 0;
}
return result;
return Kotlin.kotlin.collections.contentDeepHashCodeImpl(arr);
};
Kotlin.primitiveArraySort = function (array) {
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. 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
@UseExperimental(ExperimentalUnsignedTypes::class)
@SinceKotlin("1.3")
@kotlin.js.JsName("contentDeepHashCodeImpl")
internal fun <T> Array<out T>.contentDeepHashCodeImpl(): Int {
var result = 1
for (element in this) {
val elementHash = when {
element == null -> 0
js("Kotlin").isArrayish(element) -> (element.unsafeCast<Array<*>>()).contentDeepHashCodeImpl()
element is UByteArray -> element.contentHashCode()
element is UShortArray -> element.contentHashCode()
element is UIntArray -> element.contentHashCode()
element is ULongArray -> element.contentHashCode()
else -> element.hashCode()
}
result = 31 * result + elementHash
}
return result
}
@@ -295,9 +295,13 @@ public fun CharArray.binarySearch(element: Char, fromIndex: Int = 0, toIndex: In
* If any of arrays contains itself on any nesting level the behavior is undefined.
*/
@SinceKotlin("1.1")
@JvmName("contentDeepEqualsInline")
@kotlin.internal.InlineOnly
public actual inline infix fun <T> Array<out T>.contentDeepEquals(other: Array<out T>): Boolean {
return java.util.Arrays.deepEquals(this, other)
if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0))
return contentDeepEqualsImpl(other)
else
return java.util.Arrays.deepEquals(this, other)
}
/**
@@ -307,9 +311,13 @@ public actual inline infix fun <T> Array<out T>.contentDeepEquals(other: Array<o
* If any of arrays contains itself on any nesting level the behavior is undefined.
*/
@SinceKotlin("1.1")
@JvmName("contentDeepHashCodeInline")
@kotlin.internal.InlineOnly
public actual inline fun <T> Array<out T>.contentDeepHashCode(): Int {
return java.util.Arrays.deepHashCode(this)
if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0))
return contentDeepHashCodeImpl()
else
return java.util.Arrays.deepHashCode(this)
}
/**
@@ -322,9 +330,13 @@ public actual inline fun <T> Array<out T>.contentDeepHashCode(): Int {
* @sample samples.collections.Arrays.ContentOperations.contentDeepToString
*/
@SinceKotlin("1.1")
@JvmName("contentDeepToStringInline")
@kotlin.internal.InlineOnly
public actual inline fun <T> Array<out T>.contentDeepToString(): String {
return java.util.Arrays.deepToString(this)
if (kotlin.internal.apiVersionIsAtLeast(1, 3, 0))
return contentDeepToStringImpl()
else
return java.util.Arrays.deepToString(this)
}
/**
@@ -47,4 +47,14 @@ internal actual fun <T> arrayOfNulls(reference: Array<T>, size: Int): Array<T> {
@SinceKotlin("1.3")
internal fun copyOfRangeToIndexCheck(toIndex: Int, size: Int) {
if (toIndex > size) throw IndexOutOfBoundsException("toIndex ($toIndex) is greater than size ($size).")
}
}
@SinceKotlin("1.3")
@PublishedApi
@kotlin.jvm.JvmName("contentDeepHashCode")
internal fun <T> Array<out T>.contentDeepHashCodeImpl(): Int =
// returns valid result for unsigned arrays by accident:
// hash code of an inline class, which an unsigned array is,
// is calculated structurally as in a data class
java.util.Arrays.deepHashCode(this)
@@ -65,3 +65,95 @@ public inline fun Array<*>?.isNullOrEmpty(): Boolean {
@Suppress("UPPER_BOUND_CANNOT_BE_ARRAY")
public inline fun <C, R> C.ifEmpty(defaultValue: () -> R): R where C : Array<*>, C : R =
if (isEmpty()) defaultValue() else this
@SinceKotlin("1.3")
@PublishedApi
@kotlin.jvm.JvmName("contentDeepEquals")
@kotlin.js.JsName("contentDeepEqualsImpl")
internal fun <T> Array<out T>.contentDeepEqualsImpl(other: Array<out T>): Boolean {
if (this === other) return true
if (this.size != other.size) return false
for (i in indices) {
val v1 = this[i]
val v2 = other[i]
if (v1 === v2) {
continue
} else if (v1 == null || v2 == null) {
return false
}
when {
v1 is Array<*> && v2 is Array<*> -> if (!v1.contentDeepEquals(v2)) return false
v1 is ByteArray && v2 is ByteArray -> if (!v1.contentEquals(v2)) return false
v1 is ShortArray && v2 is ShortArray -> if (!v1.contentEquals(v2)) return false
v1 is IntArray && v2 is IntArray -> if (!v1.contentEquals(v2)) return false
v1 is LongArray && v2 is LongArray -> if (!v1.contentEquals(v2)) return false
v1 is FloatArray && v2 is FloatArray -> if (!v1.contentEquals(v2)) return false
v1 is DoubleArray && v2 is DoubleArray -> if (!v1.contentEquals(v2)) return false
v1 is CharArray && v2 is CharArray -> if (!v1.contentEquals(v2)) return false
v1 is BooleanArray && v2 is BooleanArray -> if (!v1.contentEquals(v2)) return false
v1 is UByteArray && v2 is UByteArray -> if (!v1.contentEquals(v2)) return false
v1 is UShortArray && v2 is UShortArray -> if (!v1.contentEquals(v2)) return false
v1 is UIntArray && v2 is UIntArray -> if (!v1.contentEquals(v2)) return false
v1 is ULongArray && v2 is ULongArray -> if (!v1.contentEquals(v2)) return false
else -> if (v1 != v2) return false
}
}
return true
}
@SinceKotlin("1.3")
@PublishedApi
@kotlin.jvm.JvmName("contentDeepToString")
@kotlin.js.JsName("contentDeepToStringImpl")
internal fun <T> Array<out T>.contentDeepToStringImpl(): String {
val length = size.coerceAtMost((Int.MAX_VALUE - 2) / 5) * 5 + 2 // in order not to overflow Int.MAX_VALUE
return buildString(length) {
contentDeepToStringInternal(this, mutableSetOf())
}
}
@UseExperimental(ExperimentalUnsignedTypes::class)
private fun <T> Array<out T>.contentDeepToStringInternal(result: StringBuilder, processed: MutableSet<Array<*>>) {
if (this in processed) {
result.append("[...]")
return
}
processed.add(this)
result.append('[')
for (i in indices) {
if (i != 0) {
result.append(", ")
}
val element = this[i]
when (element) {
null -> result.append("null")
is Array<*> -> element.contentDeepToStringInternal(result, processed)
is ByteArray -> result.append(element.contentToString())
is ShortArray -> result.append(element.contentToString())
is IntArray -> result.append(element.contentToString())
is LongArray -> result.append(element.contentToString())
is FloatArray -> result.append(element.contentToString())
is DoubleArray -> result.append(element.contentToString())
is CharArray -> result.append(element.contentToString())
is BooleanArray -> result.append(element.contentToString())
is UByteArray -> result.append(element.contentToString())
is UShortArray -> result.append(element.contentToString())
is UIntArray -> result.append(element.contentToString())
is ULongArray -> result.append(element.contentToString())
else -> result.append(element.toString())
}
}
result.append(']')
processed.remove(this)
}
@@ -209,10 +209,19 @@ class ArraysTest {
@Test fun contentDeepEquals() {
val arr1 = arrayOf("a", 1, intArrayOf(2))
val arr2 = arrayOf("a", 1, intArrayOf(2))
val arr3 = arrayOf("a", 1, uintArrayOf(2u))
val arr4 = arrayOf("a", 1, uintArrayOf(2u))
assertFalse(arr1 contentEquals arr2)
assertTrue(arr1 contentDeepEquals arr2)
assertFalse(arr1 contentDeepEquals arr3)
assertTrue(arr3 contentDeepEquals arr4)
arr2[2] = arr1
assertFalse(arr1 contentDeepEquals arr2)
arr4[2] = arr3
assertFalse(arr3 contentDeepEquals arr4)
}
@Test fun contentToString() {
@@ -230,8 +239,13 @@ class ArraysTest {
return
}
val arr = arrayOf("aa", 1, null, charArrayOf('d'))
assertEquals("[aa, 1, null, [d]]", arr.contentDeepToString())
val arr = arrayOf(
"aa", 1, null, arrayOf(arrayOf("foo")), charArrayOf('d'), booleanArrayOf(false),
intArrayOf(-1), longArrayOf(-1), shortArrayOf(-1), byteArrayOf(-1),
uintArrayOf(UInt.MAX_VALUE), ulongArrayOf(ULong.MAX_VALUE), ushortArrayOf(UShort.MAX_VALUE), ubyteArrayOf(UByte.MAX_VALUE),
doubleArrayOf(3.14), floatArrayOf(1.25f)
)
assertEquals("[aa, 1, null, [[foo]], [d], [false], [-1], [-1], [-1], [-1], [4294967295], [18446744073709551615], [65535], [255], [3.14], [1.25]]", arr.contentDeepToString())
}
@Test fun contentDeepToStringNoRecursion() {
@@ -262,6 +276,16 @@ class ArraysTest {
@Test fun contentDeepHashCode() {
val arr = arrayOf(null, Value(2), arrayOf(Value(3)))
assertEquals(((1*31 + 0)*31 + 2) * 31 + (1 * 31 + 3), arr.contentDeepHashCode())
val intArray2 = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
val intList2 = listOf(listOf(1, 2), listOf(3, 4))
assertEquals(intList2.hashCode(), intArray2.contentDeepHashCode())
val uintArray2 = arrayOf(uintArrayOf(1u, 2u), uintArrayOf(3u, 4u))
val uintList2 = listOf(listOf(1u, 2u), listOf(3u, 4u))
assertEquals(uintList2.hashCode(), uintArray2.contentDeepHashCode())
}