Implement in-place shuffle for arrays

Minor: reorder shuffle/shuffled extensions more consistently

#KT-25651 Fixed
This commit is contained in:
Ilya Gorbunov
2020-03-26 21:52:11 +03:00
parent e58f1c8932
commit 15319eb88e
10 changed files with 487 additions and 43 deletions
@@ -5447,6 +5447,213 @@ public fun CharArray.reversedArray(): CharArray {
return result
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun <T> Array<T>.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun ByteArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun ShortArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun IntArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun LongArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun FloatArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun DoubleArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun BooleanArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
public fun CharArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun <T> Array<T>.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun ByteArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun ShortArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun IntArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun LongArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun FloatArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun DoubleArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun BooleanArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
public fun CharArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Sorts elements in the array in-place according to natural sort order of the value returned by specified [selector] function.
*
@@ -906,6 +906,19 @@ public fun <T> Iterable<T>.reversed(): List<T> {
return list
}
/**
* Randomly shuffles elements in this list in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.3")
public fun <T> MutableList<T>.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
this[j] = this.set(i, this[j])
}
}
/**
* Sorts elements in the list in-place according to natural sort order of the value returned by specified [selector] function.
*
@@ -2702,6 +2702,106 @@ public inline fun UShortArray.reversedArray(): UShortArray {
return UShortArray(storage.reversedArray())
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun UIntArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun ULongArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun UByteArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place.
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun UShortArray.shuffle(): Unit {
shuffle(Random)
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun UIntArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun ULongArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun UByteArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Randomly shuffles elements in this array in-place using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.4")
@ExperimentalUnsignedTypes
public fun UShortArray.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
/**
* Sorts elements in the array in-place descending according to their natural sort order.
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 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.
*/
@@ -28,6 +28,20 @@ public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList
public inline fun <T> java.util.Enumeration<T>.toList(): List<T> = java.util.Collections.list(this)
/**
* Returns a new list with the elements of this list randomly shuffled.
*/
@SinceKotlin("1.2")
public actual fun <T> Iterable<T>.shuffled(): List<T> = toMutableList().apply { shuffle() }
/**
* Returns a new list with the elements of this list randomly shuffled
* using the specified [random] instance as the source of randomness.
*/
@SinceKotlin("1.2")
public fun <T> Iterable<T>.shuffled(random: java.util.Random): List<T> = toMutableList().apply { shuffle(random) }
@kotlin.internal.InlineOnly
internal actual inline fun copyToArrayImpl(collection: Collection<*>): Array<Any?> =
kotlin.jvm.internal.collectionToArray(collection)
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 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.
*/
@@ -71,16 +71,3 @@ public actual inline fun <T> MutableList<T>.shuffle() {
public inline fun <T> MutableList<T>.shuffle(random: java.util.Random) {
java.util.Collections.shuffle(this, random)
}
/**
* Returns a new list with the elements of this list randomly shuffled.
*/
@SinceKotlin("1.2")
public actual fun <T> Iterable<T>.shuffled(): List<T> = toMutableList().apply { shuffle() }
/**
* Returns a new list with the elements of this list randomly shuffled
* using the specified [random] instance as the source of randomness.
*/
@SinceKotlin("1.2")
public fun <T> Iterable<T>.shuffled(random: java.util.Random): List<T> = toMutableList().apply { shuffle(random) }
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 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.
*/
@@ -10,6 +10,7 @@
package kotlin.collections
import kotlin.contracts.*
import kotlin.random.Random
internal object EmptyIterator : ListIterator<Nothing> {
override fun hasNext(): Boolean = false
@@ -266,6 +267,15 @@ public inline fun <C, R> C.ifEmpty(defaultValue: () -> R): R where C : Collectio
@kotlin.internal.InlineOnly
public inline fun <@kotlin.internal.OnlyInputTypes T> Collection<T>.containsAll(elements: Collection<T>): Boolean = this.containsAll(elements)
/**
* Returns a new list with the elements of this list randomly shuffled
* using the specified [random] instance as the source of randomness.
*/
@SinceKotlin("1.3")
public fun <T> Iterable<T>.shuffled(random: Random): List<T> = toMutableList().apply { shuffle(random) }
internal fun <T> List<T>.optimizeReadOnlyList() = when (size) {
0 -> emptyList()
1 -> listOf(this[0])
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 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.
*/
@@ -225,14 +225,6 @@ private fun <T> MutableIterable<T>.filterInPlace(predicate: (T) -> Boolean, pred
}
/**
* Returns a new list with the elements of this list randomly shuffled
* using the specified [random] instance as the source of randomness.
*/
@SinceKotlin("1.3")
public fun <T> Iterable<T>.shuffled(random: Random): List<T> = toMutableList().apply { shuffle(random) }
/**
* Removes the element at the specified [index] from this list.
* In Kotlin one should use the [MutableList.removeAt] function instead.
@@ -307,19 +299,3 @@ private fun <T> MutableList<T>.filterInPlace(predicate: (T) -> Boolean, predicat
return false
}
}
/**
* Randomly shuffles elements in this mutable list using the specified [random] instance as the source of randomness.
*
* See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
*/
@SinceKotlin("1.3")
public fun <T> MutableList<T>.shuffle(random: Random): Unit {
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 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.
*/
@@ -1856,6 +1856,64 @@ class ArraysTest {
array.iterator().assertSorted { a, b -> comparator.compare(a, b) <= 0 }
}
private inline fun <T> testShuffle(array: T, shuffle: T.() -> Unit, toList: T.() -> List<*>) {
val original = array.toList()
array.shuffle()
val shuffled = array.toList()
assertNotEquals(original, shuffled)
assertEquals(original.groupBy { it }, shuffled.groupBy { it })
}
@Test
fun shuffle() {
val numbers = List(100) { it }
testShuffle(numbers.map(Int::toInt).toIntArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toLong).toLongArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toByte).toByteArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toShort).toShortArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toFloat).toFloatArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toDouble).toDoubleArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toChar).toCharArray(), { shuffle() }, { toList() })
testShuffle(numbers.map { it % 2 == 0 }.toBooleanArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toUInt).toUIntArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toULong).toULongArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toUByte).toUByteArray(), { shuffle() }, { toList() })
testShuffle(numbers.map(Int::toUShort).toUShortArray(), { shuffle() }, { toList() })
testShuffle(arrayOf(1, "x", null, Any(), 'a', 2u, 5.0), { shuffle() }, { toList() })
}
private inline fun <T> testShuffleR(array: T, shuffle: T.(Random) -> Unit, toList: T.() -> List<*>) {
val seed = Random.nextInt()
val original = array.toList()
val originalShuffled = original.shuffled(Random(seed))
array.shuffle(Random(seed))
val shuffled = array.toList()
assertNotEquals(original, shuffled)
assertEquals(originalShuffled, shuffled)
}
@Test
fun shufflePredictably() {
val numbers = List(16) { it }
testShuffleR(numbers.map(Int::toInt).toIntArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toLong).toLongArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toByte).toByteArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toShort).toShortArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toFloat).toFloatArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toDouble).toDoubleArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toChar).toCharArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map { it % 2 == 0 }.toBooleanArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toUInt).toUIntArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toULong).toULongArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toUByte).toUByteArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(numbers.map(Int::toUShort).toUShortArray(), { r -> shuffle(r) }, { toList() })
testShuffleR(arrayOf(1, "x", null, Any(), 'a', 2u, 5.0), { r -> shuffle(r) }, { toList() })
}
@Test
fun elementAt() {
expect(0) { byteArrayOf(0, 1, 2).elementAt(0) }
@@ -1577,6 +1577,24 @@ public final class kotlin/collections/ArraysKt {
public static final fun scanIndexed ([Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Ljava/util/List;
public static final fun scanReduce ([Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/util/List;
public static final fun scanReduceIndexed ([Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Ljava/util/List;
public static final fun shuffle ([B)V
public static final fun shuffle ([BLkotlin/random/Random;)V
public static final fun shuffle ([C)V
public static final fun shuffle ([CLkotlin/random/Random;)V
public static final fun shuffle ([D)V
public static final fun shuffle ([DLkotlin/random/Random;)V
public static final fun shuffle ([F)V
public static final fun shuffle ([FLkotlin/random/Random;)V
public static final fun shuffle ([I)V
public static final fun shuffle ([ILkotlin/random/Random;)V
public static final fun shuffle ([J)V
public static final fun shuffle ([JLkotlin/random/Random;)V
public static final fun shuffle ([Ljava/lang/Object;)V
public static final fun shuffle ([Ljava/lang/Object;Lkotlin/random/Random;)V
public static final fun shuffle ([S)V
public static final fun shuffle ([SLkotlin/random/Random;)V
public static final fun shuffle ([Z)V
public static final fun shuffle ([ZLkotlin/random/Random;)V
public static final fun single ([B)B
public static final fun single ([BLkotlin/jvm/functions/Function1;)B
public static final fun single ([C)C
@@ -2553,6 +2571,14 @@ public final class kotlin/collections/unsigned/UArraysKt {
public static final fun reversed-GBYM_sE ([B)Ljava/util/List;
public static final fun reversed-QwZRm1k ([J)Ljava/util/List;
public static final fun reversed-rL5Bavg ([S)Ljava/util/List;
public static final fun shuffle--ajY-9A ([I)V
public static final fun shuffle-2D5oskM ([ILkotlin/random/Random;)V
public static final fun shuffle-GBYM_sE ([B)V
public static final fun shuffle-JzugnMA ([JLkotlin/random/Random;)V
public static final fun shuffle-QwZRm1k ([J)V
public static final fun shuffle-oSF2wD8 ([BLkotlin/random/Random;)V
public static final fun shuffle-rL5Bavg ([S)V
public static final fun shuffle-s5X_as8 ([SLkotlin/random/Random;)V
public static final fun singleOrNull--ajY-9A ([I)Lkotlin/UInt;
public static final fun singleOrNull-GBYM_sE ([B)Lkotlin/UByte;
public static final fun singleOrNull-QwZRm1k ([J)Lkotlin/ULong;
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2020 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.
*/
@@ -457,4 +457,57 @@ object Ordering : TemplateGroupBase() {
}
}
val f_shuffle = fn("shuffle()") {
include(InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
} builder {
since("1.4")
returns("Unit")
doc {
"""
Randomly shuffles elements in this ${f.collection} in-place.
"""
}
body {
"shuffle(Random)"
}
}
val f_shuffleRandom = fn("shuffle(random: Random)") {
include(Lists, InvariantArraysOfObjects, ArraysOfPrimitives, ArraysOfUnsigned)
} builder {
since("1.4")
returns("Unit")
doc {
"""
Randomly shuffles elements in this ${f.collection} in-place using the specified [random] instance as the source of randomness.
See: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
"""
}
specialFor(Lists) {
since("1.3")
receiver("MutableList<T>")
}
body {
"""
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
val copy = this[i]
this[i] = this[j]
this[j] = copy
}
"""
}
specialFor(Lists) {
body {
"""
for (i in lastIndex downTo 1) {
val j = random.nextInt(i + 1)
this[j] = this.set(i, this[j])
}
"""
}
}
}
}