diff --git a/js/js.libraries/src/core/generated/_SequencesJs.kt b/js/js.libraries/src/core/generated/_SequencesJs.kt index 745070fd35c..175a57f268d 100644 --- a/js/js.libraries/src/core/generated/_SequencesJs.kt +++ b/js/js.libraries/src/core/generated/_SequencesJs.kt @@ -1502,6 +1502,7 @@ public fun Sequence.windowed(size: Int, step: Int): Sequence> { @SinceKotlin("1.2") public fun Sequence.windowed(size: Int, step: Int, transform: (List) -> R): Sequence { + require(size > 0 && step > 0) { "size $size and step $step both must be greater than zero" } return Sequence { windowForwardOnlySequenceImpl(iterator(), size, step, dropTrailing = false).iterator() }.map(transform) } diff --git a/libraries/stdlib/src/generated/_Sequences.kt b/libraries/stdlib/src/generated/_Sequences.kt index ebb97024bfe..b0e072be2ae 100644 --- a/libraries/stdlib/src/generated/_Sequences.kt +++ b/libraries/stdlib/src/generated/_Sequences.kt @@ -1524,6 +1524,7 @@ public fun Sequence.windowed(size: Int, step: Int): Sequence> { @SinceKotlin("1.2") public fun Sequence.windowed(size: Int, step: Int, transform: (List) -> R): Sequence { + require(size > 0 && step > 0) { "size $size and step $step both must be greater than zero" } return Sequence { windowForwardOnlySequenceImpl(iterator(), size, step, dropTrailing = false).iterator() }.map(transform) } diff --git a/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt b/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt index 62b034552a8..532771dd2a6 100644 --- a/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt +++ b/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt @@ -18,8 +18,7 @@ package kotlin.collections internal fun windowIndices(sourceSize: Int, size: Int, step: Int, dropTrailing: Boolean): Sequence { - require(size >= 0) { "size should not be negative" } - require(step != 0) { "step shouldn't be zero" } + require(size > 0 && step > 0) { "size $size and step $step both must be greater than zero" } if (sourceSize == 0 || (size > sourceSize && dropTrailing)) { return emptySequence() @@ -54,8 +53,7 @@ internal fun windowIndices(sourceSize: Int, size: Int, step: Int, dropTrailing: } internal fun windowForwardOnlySequenceImpl(iterator: Iterator, size: Int, step: Int, dropTrailing: Boolean): Sequence> { - require(size >= 0) { "size should not be negative" } - require(step > 0) { "step should be positive non zero" } + require(size > 0 && step > 0) { "size $size and step $step both must be greater than zero" } return if (step >= size) { windowForwardWithGap(iterator, size, step, dropTrailing) @@ -278,8 +276,9 @@ internal class RingBuffer(val capacity: Int): Iterable { return if (this < n) (this - n + capacity) else this - n } + // TODO: replace with Array.fill from stdlib when available in common private fun Array.fill(element: T, fromIndex: Int = 0, toIndex: Int = size): Unit { - for (idx in fromIndex .. toIndex) { + for (idx in fromIndex .. toIndex-1) { this[idx] = element } } diff --git a/libraries/stdlib/test/collections/IterableTests.kt b/libraries/stdlib/test/collections/IterableTests.kt index 4868830eb39..68a2a338c2e 100644 --- a/libraries/stdlib/test/collections/IterableTests.kt +++ b/libraries/stdlib/test/collections/IterableTests.kt @@ -135,6 +135,67 @@ abstract class OrderedIterableTests>(createFrom: (Array + assertEquals(expectedSingleChunk, data.chunked(size).single()) + assertEquals(expectedSingleChunk, data.chunked(size + 3).single()) + } + + assertTrue(empty.chunked(3).isEmpty()) + + for (illegalValue in listOf(Int.MIN_VALUE, -1, 0)) { + assertFailsWith("size $illegalValue") { data.chunked(illegalValue) } + } + } + + + @Test + fun windowed() { + val size = 7 + val data = createFrom(Array(size) { "$it" }) + val result = data.windowed(4, 2) + assertEquals(listOf( + listOf("0", "1", "2", "3"), + listOf("2", "3", "4", "5"), + listOf("4", "5", "6"), + listOf("6") + ), result) + + val result2 = data.windowed(2, 3) { it.joinToString("") } + assertEquals(listOf("01", "34", "6"), result2) + + assertEquals(data.chunked(2), data.windowed(2, 2)) + + assertEquals(data.take(2), data.windowed(2, size).single()) + assertEquals(data.take(3), data.windowed(3, size + 3).single()) + + val result3 = data.windowed(size, 1) + result3.forEachIndexed { index, window -> + assertEquals(size - index, window.size, "size of window#$index") + } + + assertTrue(empty.windowed(3, 2).isEmpty()) + + for (illegalValue in listOf(Int.MIN_VALUE, -1, 0)) { + assertFailsWith("size $illegalValue") { data.windowed(illegalValue, 1) } + assertFailsWith("step $illegalValue") { data.windowed(1, illegalValue) } + } + } + + } abstract class IterableTests>(val createFrom: (Array) -> T, val empty: T) { diff --git a/libraries/stdlib/test/collections/SequenceTest.kt b/libraries/stdlib/test/collections/SequenceTest.kt index 7bbf2b50495..7860d64dcbd 100644 --- a/libraries/stdlib/test/collections/SequenceTest.kt +++ b/libraries/stdlib/test/collections/SequenceTest.kt @@ -216,6 +216,79 @@ public class SequenceTest { ensureIsIntermediate(source = sequenceOf(1, 2)) { it.pairwise() } } + @Test + fun chunked() { + val infiniteSeq = generateSequence(0) { it + 1 } + val result = infiniteSeq.chunked(4) + assertEquals(listOf( + listOf(0, 1, 2, 3), + listOf(4, 5, 6, 7) + ), result.take(2).toList()) + + val size = 7 + val seq = infiniteSeq.take(7) + + val result2 = seq.chunked(3) { it.joinToString("") } + assertEquals(listOf("012", "345", "6"), result2.toList()) + + seq.toList().let { expectedSingleChunk -> + assertEquals(expectedSingleChunk, seq.chunked(size).single()) + assertEquals(expectedSingleChunk, seq.chunked(size + 3).single()) + } + + assertTrue(emptySequence().chunked(3).none()) + + for (illegalValue in listOf(Int.MIN_VALUE, -1, 0)) { + assertFailsWith("size $illegalValue") { infiniteSeq.chunked(illegalValue) } + } + + ensureIsIntermediate(source = sequenceOf(1, 2, 3)) { it.chunked(2) } + } + + + @Test + fun windowed() { + val infiniteSeq = generateSequence(0) { it + 1 } + val result = infiniteSeq.windowed(5, 3) + result.take(10).forEachIndexed { windowIndex, window -> + val startElement = windowIndex * 3 + assertEquals((startElement until startElement + 5).toList(), window) + } + + val size = 7 + val seq = infiniteSeq.take(7) + + val result1 = seq.windowed(4, 2) + assertEquals(listOf( + listOf(0, 1, 2, 3), + listOf(2, 3, 4, 5), + listOf(4, 5, 6), + listOf(6) + ), result1.toList()) + + val result2 = seq.windowed(2, 3) { it.joinToString("") } + assertEquals(listOf("01", "34", "6"), result2.toList()) + + assertEquals(seq.chunked(2).toList(), seq.windowed(2, 2).toList()) + + assertEquals(seq.take(2).toList(), seq.windowed(2, size).single()) + assertEquals(seq.take(3).toList(), seq.windowed(3, size + 3).single()) + + val result3 = seq.windowed(size, 1) + result3.forEachIndexed { index, window -> + assertEquals(size - index, window.size, "size of window#$index") + } + + assertTrue(emptySequence().windowed(3, 2).none()) + + for (illegalValue in listOf(Int.MIN_VALUE, -1, 0)) { + assertFailsWith("size $illegalValue") { seq.windowed(illegalValue, 1) } + assertFailsWith("step $illegalValue") { seq.windowed(1, illegalValue) } + } + + ensureIsIntermediate(source = sequenceOf(1, 2, 3)) { it.windowed(2, 1) } + } + @Test fun zip() { expect(listOf("ab", "bc", "cd")) { sequenceOf("a", "b", "c").zip(sequenceOf("b", "c", "d")) { a, b -> a + b }.toList() diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index b151f68dc7c..83000861eeb 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -19,6 +19,8 @@ package test.text import kotlin.test.* import test.* import org.junit.Test +import test.collections.behaviors.iteratorBehavior +import test.collections.compare fun createString(content: String): CharSequence = content @@ -849,6 +851,67 @@ class StringTest { assertTrue(arg1("a").pairwise().isEmpty()) } + + @Test + fun chunked() = withOneCharSequenceArg { arg1 -> + val size = 7 + val data = arg1("abcdefg") + val result = data.chunked(4) + assertEquals(listOf("abcd", "efg"), result) + + val result2 = data.chunked(3) { it.reversed().toString() } + assertEquals(listOf("cba", "fed", "g"), result2) + + data.toString().let { expectedSingleChunk -> + assertEquals(expectedSingleChunk, data.chunked(size).single()) + assertEquals(expectedSingleChunk, data.chunked(size + 3).single()) + } + + assertTrue(arg1("").chunked(3).isEmpty()) + + for (illegalValue in listOf(Int.MIN_VALUE, -1, 0)) { + assertFailsWith("size $illegalValue") { data.chunked(illegalValue) } + } + + for (chunkSize in 1..size + 1) { + compare(data.chunked(chunkSize).iterator(), data.chunkedSequence(chunkSize).iterator()) { iteratorBehavior() } + } + } + + + @Test + fun windowed() = withOneCharSequenceArg { arg1 -> + val size = 7 + val data = arg1("abcdefg") + val result = data.windowed(4, 2) + assertEquals(listOf("abcd", "cdef", "efg", "g"), result) + + val result2 = data.windowed(2, 3) { it.reversed().toString() } + assertEquals(listOf("ba", "ed", "g"), result2) + + assertEquals(data.chunked(2), data.windowed(2, 2)) + + assertEquals(data.take(2), data.windowed(2, size).single()) + assertEquals(data.take(3), data.windowed(3, size + 3).single()) + + val result3 = data.windowed(size, 1) + result3.forEachIndexed { index, window -> + assertEquals(size - index, window.length, "size of window#$index") + } + + assertTrue(arg1("").windowed(3, 2).isEmpty()) + + for (illegalValue in listOf(Int.MIN_VALUE, -1, 0)) { + assertFailsWith("size $illegalValue") { data.windowed(illegalValue, 1) } + assertFailsWith("step $illegalValue") { data.windowed(1, illegalValue) } + } + + for (window in 1..size + 1) { + for (step in 1..size + 1) + compare(data.windowed(window, step).iterator(), data.windowedSequence(window, step).iterator()) { iteratorBehavior() } + } + } + @Test fun map() = withOneCharSequenceArg { arg1 -> assertEquals(listOf('a', 'b', 'c'), arg1("abc").map { it }) diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt index 70034483698..3682ff6a11a 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt @@ -585,6 +585,7 @@ fun generators(): List { returns(Sequences) { "Sequence" } body(Sequences) { """ + require(size > 0 && step > 0) { "size ${"$"}size and step ${"$"}step both must be greater than zero" } return Sequence { windowForwardOnlySequenceImpl(iterator(), size, step, dropTrailing = false).iterator() }.map(transform) """ }