From e58f1c8932c057a950d55d82a15f98a7f567c3f5 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 26 Mar 2020 22:06:59 +0300 Subject: [PATCH] Introduce Sequence.shuffled #KT-37751 --- .../src/kotlin/collections/Sequences.kt | 34 ++++++++++- .../stdlib/test/collections/SequenceTest.kt | 57 ++++++++++++++++++- .../kotlin-stdlib-runtime-merged.txt | 2 + 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/libraries/stdlib/src/kotlin/collections/Sequences.kt b/libraries/stdlib/src/kotlin/collections/Sequences.kt index f5966e73523..3bbef77d77c 100644 --- a/libraries/stdlib/src/kotlin/collections/Sequences.kt +++ b/libraries/stdlib/src/kotlin/collections/Sequences.kt @@ -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. */ @@ -8,6 +8,8 @@ package kotlin.sequences +import kotlin.random.Random + /** * Given an [iterator] function constructs a [Sequence] that returns values through the [Iterator] * provided by that function. @@ -109,6 +111,36 @@ public fun Sequence>.unzip(): Pair, List> { return listT to listR } +/** + * Returns a sequence that yields elements of this sequence randomly shuffled. + * + * Note that every iteration of the sequence returns elements in a different order. + * + * The operation is _intermediate_ and _stateful_. + */ +@SinceKotlin("1.4") +public fun Sequence.shuffled(): Sequence = shuffled(Random) + +/** + * Returns a sequence that yields elements of this sequence randomly shuffled + * using the specified [random] instance as the source of randomness. + * + * Note that every iteration of the sequence returns elements in a different order. + * + * The operation is _intermediate_ and _stateful_. + */ +@SinceKotlin("1.4") +public fun Sequence.shuffled(random: Random): Sequence = sequence { + val buffer = toMutableList() + while (buffer.isNotEmpty()) { + val j = random.nextInt(buffer.size) + val last = @OptIn(ExperimentalStdlibApi::class) buffer.removeLast() + val value = if (j < buffer.size) buffer.set(j, last) else last + yield(value) + } +} + + /** * A sequence that returns the values from the underlying [sequence] that either match or do not match * the specified [predicate]. diff --git a/libraries/stdlib/test/collections/SequenceTest.kt b/libraries/stdlib/test/collections/SequenceTest.kt index 528d8b08313..67373311aba 100644 --- a/libraries/stdlib/test/collections/SequenceTest.kt +++ b/libraries/stdlib/test/collections/SequenceTest.kt @@ -1,10 +1,11 @@ /* - * 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. */ package test.collections +import kotlin.random.Random import kotlin.test.* fun fibonacci(): Sequence { @@ -633,6 +634,60 @@ public class SequenceTest { assertEquals(listOf("act", "wast", "test"), sequenceOf("act", "test", "wast").sortedWith(comparator).toList()) } + @Test fun shuffled() { + val sequence = (0 until 100).asSequence() + val originalValues = sequence.toList() + val shuffled = sequence.shuffled() + val values1 = shuffled.toList() + val values2 = shuffled.toList() + + assertNotEquals(originalValues, values1) + assertNotEquals(values1, values2, "Each run returns new shuffle") + assertEquals(originalValues.toSet(), values1.toSet()) + assertEquals(originalValues.toSet(), values2.toSet()) + assertEquals(originalValues.size, values1.distinct().size) + assertEquals(originalValues.size, values2.distinct().size) + } + + @Test fun shuffledPredictably() { + val list = List(10) { it } + val sequence = list.asSequence() + val shuffled1 = sequence.shuffled(Random(1)) + val shuffled2 = sequence.shuffled(Random(1)) + + val values1 = shuffled1.toList() + val values2 = shuffled2.toList() + + assertEquals(values1, values2) + assertEquals("[5, 3, 7, 9, 8, 2, 6, 0, 4, 1]", values1.toString()) + + val values1n = shuffled1.toList() + assertNotEquals(values1, values1n, "Each run returns new shuffle") + + val values42 = sequence.shuffled(Random(42)).toList() + assertEquals("[3, 6, 7, 1, 8, 2, 9, 4, 0, 5]", values42.toString()) + } + + @Test fun shuffledPartially() { + val countingRandom = object : Random() { + var counter: Int = 0 + override fun nextBits(bitCount: Int): Int { + counter++ + return Random.nextBits(bitCount) + } + } + + val sequence = (0 until 100).asSequence() + val partialShuffle = sequence.shuffled(countingRandom).take(10) + + assertEquals(0, countingRandom.counter) + + val result = partialShuffle.toList() + assertEquals(10, result.size) + assertEquals(10, countingRandom.counter) + } + + @Test fun associateWith() { val items = sequenceOf("Alice", "Bob", "Carol") val itemsWithTheirLength = items.associateWith { it.length } 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 7dbb7ca3886..2455e8fac4b 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 @@ -4714,6 +4714,8 @@ public final class kotlin/sequences/SequencesKt { public static final fun scanReduceIndexed (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function3;)Lkotlin/sequences/Sequence; public static final fun sequence (Lkotlin/jvm/functions/Function2;)Lkotlin/sequences/Sequence; public static final fun sequenceOf ([Ljava/lang/Object;)Lkotlin/sequences/Sequence; + public static final fun shuffled (Lkotlin/sequences/Sequence;)Lkotlin/sequences/Sequence; + public static final fun shuffled (Lkotlin/sequences/Sequence;Lkotlin/random/Random;)Lkotlin/sequences/Sequence; public static final fun single (Lkotlin/sequences/Sequence;)Ljava/lang/Object; public static final fun single (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun singleOrNull (Lkotlin/sequences/Sequence;)Ljava/lang/Object;