diff --git a/js/js.libraries/src/core/generated/_CollectionsJs.kt b/js/js.libraries/src/core/generated/_CollectionsJs.kt index 4b1689a9963..d5c0bb681fb 100644 --- a/js/js.libraries/src/core/generated/_CollectionsJs.kt +++ b/js/js.libraries/src/core/generated/_CollectionsJs.kt @@ -1790,7 +1790,7 @@ public fun List.requireNoNulls(): List { @SinceKotlin("1.2") public fun Iterable.chunked(size: Int): List> { - return chunked(size) { it.toList() } + return windowed(size, size) } @SinceKotlin("1.2") @@ -1988,15 +1988,44 @@ public inline fun Collection.plusElement(element: T): List { @SinceKotlin("1.2") public fun Iterable.windowed(size: Int, step: Int): List> { - return windowed(size, step) { it.toList() } + checkWindowSizeStep(size, step) + if (this is RandomAccess && this is List) { + val thisSize = this.size + val result = ArrayList>((thisSize + step - 1) / step) + var index = 0 + while (index < thisSize) { + result.add(List(size.coerceAtMost(thisSize - index)) { this[it + index] }) + index += step + } + return result + } + val result = ArrayList>() + windowedIterator(iterator(), size, step, dropTrailing = false, reuseBuffer = false).forEach { + result.add(it) + } + return result } @SinceKotlin("1.2") public fun Iterable.windowed(size: Int, step: Int, transform: (List) -> R): List { - if (this is List) { - return windowIndices(this.size, size, step, dropTrailing = false).asIterable().map { transform(subList(it.start, it.endInclusive + 1)) } + checkWindowSizeStep(size, step) + if (this is RandomAccess && this is List) { + val thisSize = this.size + val result = ArrayList((thisSize + step - 1) / step) + val window = MovingSubList(this) + var index = 0 + while (index < thisSize) { + window.move(index, (index + size).coerceAtMost(thisSize)) + result.add(transform(window)) + index += step + } + return result } - return windowForwardOnlySequenceImpl(iterator(), size, step, dropTrailing = false).asIterable().map(transform) + val result = ArrayList() + windowedIterator(iterator(), size, step, dropTrailing = false, reuseBuffer = true).forEach { + result.add(transform(it)) + } + return result } /** diff --git a/js/js.libraries/src/core/generated/_SequencesJs.kt b/js/js.libraries/src/core/generated/_SequencesJs.kt index 175a57f268d..f2d4d8e35c9 100644 --- a/js/js.libraries/src/core/generated/_SequencesJs.kt +++ b/js/js.libraries/src/core/generated/_SequencesJs.kt @@ -1311,7 +1311,7 @@ public fun Sequence.requireNoNulls(): Sequence { @SinceKotlin("1.2") public fun Sequence.chunked(size: Int): Sequence> { - return chunked(size) { it.toList() } + return windowed(size, size) } @SinceKotlin("1.2") @@ -1497,13 +1497,12 @@ public inline fun Sequence.plusElement(element: T): Sequence { @SinceKotlin("1.2") public fun Sequence.windowed(size: Int, step: Int): Sequence> { - return windowed(size, step) { it.toList() } + return windowedSequence(size, step, dropTrailing = false, reuseBuffer = false) } @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) + return windowedSequence(size, step, dropTrailing = false, reuseBuffer = true).map(transform) } /** diff --git a/js/js.libraries/src/core/generated/_StringsJs.kt b/js/js.libraries/src/core/generated/_StringsJs.kt index 111d7f54be8..db59b1ddd05 100644 --- a/js/js.libraries/src/core/generated/_StringsJs.kt +++ b/js/js.libraries/src/core/generated/_StringsJs.kt @@ -1104,7 +1104,7 @@ public inline fun CharSequence.sumByDouble(selector: (Char) -> Double): Double { @SinceKotlin("1.2") public fun CharSequence.chunked(size: Int): List { - return chunked(size) { it.toString() } + return windowed(size, size) } @SinceKotlin("1.2") @@ -1181,7 +1181,15 @@ public fun CharSequence.windowed(size: Int, step: Int): List { @SinceKotlin("1.2") public fun CharSequence.windowed(size: Int, step: Int, transform: (CharSequence) -> R): List { - return windowIndices(this.length, size, step, dropTrailing = false).asIterable().map { transform(subSequence(it)) } + checkWindowSizeStep(size, step) + val thisSize = this.length + val result = ArrayList((thisSize + step - 1) / step) + var index = 0 + while (index < thisSize) { + result.add(transform(subSequence(index, (index + size).coerceAtMost(thisSize)))) + index += step + } + return result } @SinceKotlin("1.2") @@ -1191,7 +1199,8 @@ public fun CharSequence.windowedSequence(size: Int, step: Int): Sequence @SinceKotlin("1.2") public fun CharSequence.windowedSequence(size: Int, step: Int, transform: (CharSequence) -> R): Sequence { - return windowIndices(this.length, size, step, dropTrailing = false).map { transform(subSequence(it)) } + checkWindowSizeStep(size, step) + return (indices step step).asSequence().map { index -> transform(subSequence(index, (index + size).coerceAtMost(length))) } } /** diff --git a/libraries/stdlib/src/generated/_Collections.kt b/libraries/stdlib/src/generated/_Collections.kt index 047ab5d819c..02231be88fb 100644 --- a/libraries/stdlib/src/generated/_Collections.kt +++ b/libraries/stdlib/src/generated/_Collections.kt @@ -1800,7 +1800,7 @@ public fun List.requireNoNulls(): List { @SinceKotlin("1.2") public fun Iterable.chunked(size: Int): List> { - return chunked(size) { it.toList() } + return windowed(size, size) } @SinceKotlin("1.2") @@ -1998,15 +1998,44 @@ public inline fun Collection.plusElement(element: T): List { @SinceKotlin("1.2") public fun Iterable.windowed(size: Int, step: Int): List> { - return windowed(size, step) { it.toList() } + checkWindowSizeStep(size, step) + if (this is RandomAccess && this is List) { + val thisSize = this.size + val result = ArrayList>((thisSize + step - 1) / step) + var index = 0 + while (index < thisSize) { + result.add(List(size.coerceAtMost(thisSize - index)) { this[it + index] }) + index += step + } + return result + } + val result = ArrayList>() + windowedIterator(iterator(), size, step, dropTrailing = false, reuseBuffer = false).forEach { + result.add(it) + } + return result } @SinceKotlin("1.2") public fun Iterable.windowed(size: Int, step: Int, transform: (List) -> R): List { - if (this is List) { - return windowIndices(this.size, size, step, dropTrailing = false).asIterable().map { transform(subList(it.start, it.endInclusive + 1)) } + checkWindowSizeStep(size, step) + if (this is RandomAccess && this is List) { + val thisSize = this.size + val result = ArrayList((thisSize + step - 1) / step) + val window = MovingSubList(this) + var index = 0 + while (index < thisSize) { + window.move(index, (index + size).coerceAtMost(thisSize)) + result.add(transform(window)) + index += step + } + return result } - return windowForwardOnlySequenceImpl(iterator(), size, step, dropTrailing = false).asIterable().map(transform) + val result = ArrayList() + windowedIterator(iterator(), size, step, dropTrailing = false, reuseBuffer = true).forEach { + result.add(transform(it)) + } + return result } /** diff --git a/libraries/stdlib/src/generated/_Sequences.kt b/libraries/stdlib/src/generated/_Sequences.kt index b0e072be2ae..077a7e360dc 100644 --- a/libraries/stdlib/src/generated/_Sequences.kt +++ b/libraries/stdlib/src/generated/_Sequences.kt @@ -1333,7 +1333,7 @@ public fun Sequence.requireNoNulls(): Sequence { @SinceKotlin("1.2") public fun Sequence.chunked(size: Int): Sequence> { - return chunked(size) { it.toList() } + return windowed(size, size) } @SinceKotlin("1.2") @@ -1519,13 +1519,12 @@ public inline fun Sequence.plusElement(element: T): Sequence { @SinceKotlin("1.2") public fun Sequence.windowed(size: Int, step: Int): Sequence> { - return windowed(size, step) { it.toList() } + return windowedSequence(size, step, dropTrailing = false, reuseBuffer = false) } @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) + return windowedSequence(size, step, dropTrailing = false, reuseBuffer = true).map(transform) } /** diff --git a/libraries/stdlib/src/generated/_Strings.kt b/libraries/stdlib/src/generated/_Strings.kt index c970c1582af..cce2f84ac2c 100644 --- a/libraries/stdlib/src/generated/_Strings.kt +++ b/libraries/stdlib/src/generated/_Strings.kt @@ -1112,7 +1112,7 @@ public inline fun CharSequence.sumByDouble(selector: (Char) -> Double): Double { @SinceKotlin("1.2") public fun CharSequence.chunked(size: Int): List { - return chunked(size) { it.toString() } + return windowed(size, size) } @SinceKotlin("1.2") @@ -1189,7 +1189,15 @@ public fun CharSequence.windowed(size: Int, step: Int): List { @SinceKotlin("1.2") public fun CharSequence.windowed(size: Int, step: Int, transform: (CharSequence) -> R): List { - return windowIndices(this.length, size, step, dropTrailing = false).asIterable().map { transform(subSequence(it)) } + checkWindowSizeStep(size, step) + val thisSize = this.length + val result = ArrayList((thisSize + step - 1) / step) + var index = 0 + while (index < thisSize) { + result.add(transform(subSequence(index, (index + size).coerceAtMost(thisSize)))) + index += step + } + return result } @SinceKotlin("1.2") @@ -1199,7 +1207,8 @@ public fun CharSequence.windowedSequence(size: Int, step: Int): Sequence @SinceKotlin("1.2") public fun CharSequence.windowedSequence(size: Int, step: Int, transform: (CharSequence) -> R): Sequence { - return windowIndices(this.length, size, step, dropTrailing = false).map { transform(subSequence(it)) } + checkWindowSizeStep(size, step) + return (indices step step).asSequence().map { index -> transform(subSequence(index, (index + size).coerceAtMost(length))) } } /** diff --git a/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt b/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt index 532771dd2a6..ef7000dc7f9 100644 --- a/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt +++ b/libraries/stdlib/src/kotlin/collections/SlidingWindow.kt @@ -16,119 +16,90 @@ package kotlin.collections +import kotlin.coroutines.experimental.buildIterator -internal fun windowIndices(sourceSize: Int, size: Int, step: Int, dropTrailing: Boolean): Sequence { - require(size > 0 && step > 0) { "size $size and step $step both must be greater than zero" } - - if (sourceSize == 0 || (size > sourceSize && dropTrailing)) { - return emptySequence() - } - if (size == 0) { - return when { - step > 0 -> (0 .. sourceSize - 1 step step) - else -> (sourceSize - 1 downTo 0 step -step) - }.asSequence().map { it .. it - 1 } // empty ranges with valid start - } - - var currentIndex = when { - step > 0 -> 0 - else -> sourceSize - size - } - - return generateSequence { - val startIndex = currentIndex - val endExclusive = currentIndex + size - - when { - startIndex >= sourceSize -> null - endExclusive > sourceSize && dropTrailing -> null - startIndex < 0 && dropTrailing -> null - step < 0 && endExclusive <= 0 -> null - else -> { - currentIndex += step - startIndex.coerceAtLeast(0) .. endExclusive.coerceAtMost(sourceSize) - 1 - } - } +internal fun checkWindowSizeStep(size: Int, step: Int) { + require(size > 0 && step > 0) { + if (size != step) + "Both size $size and step $step must be greater than zero." + else + "size $size must be greater than zero." } } -internal fun windowForwardOnlySequenceImpl(iterator: Iterator, size: Int, step: Int, dropTrailing: Boolean): Sequence> { - 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) - } else { - windowForwardWithOverlap(iterator, size, step, dropTrailing) - } +internal fun Sequence.windowedSequence(size: Int, step: Int, dropTrailing: Boolean, reuseBuffer: Boolean): Sequence> { + checkWindowSizeStep(size, step) + return Sequence { windowedIterator(iterator(), size, step, dropTrailing, reuseBuffer) } } -private fun windowForwardWithGap(iterator: Iterator, size: Int, step: Int, dropTrailing: Boolean): Sequence> { - require(step >= size) - var first = true - val gap = step - size - - fun skipGap() { - for (skip in 1..gap) { - if (!iterator.hasNext()) { - break +internal fun windowedIterator(iterator: Iterator, size: Int, step: Int, dropTrailing: Boolean, reuseBuffer: Boolean): Iterator> { + if (!iterator.hasNext()) return EmptyIterator + return buildIterator> { + val gap = step - size + if (gap >= 0) { + var buffer = ArrayList(size) + var skip = 0 + for (e in iterator) { + if (skip > 0) { skip -= 1; continue } + buffer.add(e) + if (buffer.size == size) { + yield(buffer) + if (reuseBuffer) buffer.clear() else buffer = ArrayList(size) + skip = gap + } + } + if (buffer.isNotEmpty()) { + if (!dropTrailing || buffer.size == size) yield(buffer) } - iterator.next() - } - } - - return generateSequence { - if (first) { - first = false } else { - skipGap() - } - - val buffer = ArrayList(size) - for (i in 1..size) { - if (!iterator.hasNext()) { - break + val buffer = RingBuffer(size) + for (e in iterator) { + buffer.add(e) + if (buffer.isFull()) { + yield(if (reuseBuffer) buffer else ArrayList(buffer)) + buffer.removeFirst(step) + } + } + if (dropTrailing) { + if (buffer.size == size) yield(buffer) + } else { + while (buffer.size > step) { + yield(if (reuseBuffer) buffer else ArrayList(buffer)) + buffer.removeFirst(step) + } + if (buffer.isNotEmpty()) yield(buffer) } - buffer.add(iterator.next()) - } - - when { - buffer.isEmpty() && !iterator.hasNext() -> null - buffer.size < size && dropTrailing -> null - else -> buffer } } } -private fun windowForwardWithOverlap(iterator: Iterator, size: Int, step: Int, dropTrailing: Boolean): Sequence> { - require(step < size) +internal class MovingSubList(private val list: List) : AbstractList(), RandomAccess { + private var fromIndex: Int = 0 + private var _size: Int = 0 - val buffer = RingBuffer(size) - - return generateSequence { - if (!buffer.isEmpty()) { - buffer.removeFirst(minOf(step, buffer.size)) - } - - while (!buffer.isFull() && iterator.hasNext()) { - buffer.add(iterator.next()) - } - - @Suppress("UNCHECKED_CAST") - when { - buffer.isEmpty() && !iterator.hasNext() -> null - !buffer.isFull() && dropTrailing -> null - else -> buffer.toArray().asList() as List - } + fun move(fromIndex: Int, toIndex: Int) { + checkRangeIndexes(fromIndex, toIndex, list.size) + this.fromIndex = fromIndex + this._size = toIndex - fromIndex } + + override fun get(index: Int): E { + checkElementIndex(index, _size) + + return list[fromIndex + index] + } + + override val size: Int get() = _size } + /** * Provides ring buffer implementation. * * Buffer overflow is not allowed so [add] doesn't overwrite tail but raises an exception while [offer] returns `false` * If it is going to be public API perhaps this behaviour could be customizable */ -internal class RingBuffer(val capacity: Int): Iterable { +private class RingBuffer(val capacity: Int): AbstractList(), RandomAccess { init { require(capacity >= 0) { "ring buffer capacity should not be negative but it is $capacity" } } @@ -136,31 +107,32 @@ internal class RingBuffer(val capacity: Int): Iterable { private val buffer = arrayOfNulls(capacity) private var writePosition = 0 - var size: Int = 0 + override var size: Int = 0 private set - fun isEmpty() = size == 0 + override fun get(index: Int): T { + checkElementIndex(index, size) + return getAtUnsafe(writePosition.backward(size - index)) + } + fun isFull() = size == capacity - override fun iterator(): Iterator = when { - isEmpty() -> EmptyIterator - else -> object : AbstractIterator() { + override fun iterator(): Iterator = object : AbstractIterator() { private var count = size - private var idx = writePosition.backward(count) + private var index = writePosition.backward(size) override fun computeNext() { if (count == 0) { done() } else { - setNext(getAtUnsafe(idx)) - idx = idx.forward() + setNext(getAtUnsafe(index)) + index = index.forward(1) count-- } } - } } - fun toArray(): Array { + override fun toArray(): Array { val size = this.size val result = arrayOfNulls(size) var widx = 0 @@ -187,7 +159,7 @@ internal class RingBuffer(val capacity: Int): Iterable { */ fun add(element: T) { if (!offer(element)) { - throw IllegalStateException("ring buffer is full") + throw IllegalStateException("Ring buffer is full.") } } @@ -200,29 +172,11 @@ internal class RingBuffer(val capacity: Int): Iterable { } buffer[writePosition] = element - writePosition = writePosition.forward() + writePosition = writePosition.forward(1) size++ return true } - /** - * Takes first element from the buffer or fails with [NoSuchElementException] if the buffer is empty - */ - fun get(): T { - if (isEmpty()) { - throw NoSuchElementException("ring buffer is empty") - } - - val readPosition = writePosition.backward(size) - - val result = getAtUnsafe(readPosition) - buffer[readPosition] = null - - size-- - - return result - } - /** * Removes [n] first elements from the buffer or fails with [IllegalArgumentException] if not enough elements in the buffer to remove */ @@ -234,9 +188,6 @@ internal class RingBuffer(val capacity: Int): Iterable { val start = writePosition.backward(size) val end = start.forward(n - 1) - for (i in start .. end) { - buffer[i] = null - } if (start > end) { buffer.fill(null, start, capacity) buffer.fill(null, 0, end + 1) @@ -248,33 +199,15 @@ internal class RingBuffer(val capacity: Int): Iterable { } } - /** - * Removes all elements from the buffer - */ - fun clear() { - size = 0 - buffer.fill(null) - } @Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") private inline fun getAtUnsafe(idx: Int): T = buffer[idx] as T @Suppress("NOTHING_TO_INLINE") - private inline fun Int.forward(n: Int = 1): Int { - require(n >= 0) - require(n <= capacity) - - val result = this + n - return if (result >= capacity) result - capacity else result - } + private inline fun Int.forward(n: Int): Int = (this + n) % capacity @Suppress("NOTHING_TO_INLINE") - private inline fun Int.backward(n: Int = 1): Int { - require(n >= 0) - require(n <= capacity) - - return if (this < n) (this - n + capacity) else this - n - } + private inline fun Int.backward(n: Int): Int = ((this - n) % capacity + capacity) % capacity // TODO: replace with Array.fill from stdlib when available in common private fun Array.fill(element: T, fromIndex: Int = 0, toIndex: Int = size): Unit { diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt index 3682ff6a11a..07b7be25390 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Generators.kt @@ -568,25 +568,46 @@ fun generators(): List { body { """ - if (this is List) { - return windowIndices(this.size, size, step, dropTrailing = false).asIterable().map { transform(subList(it.start, it.endInclusive + 1)) } + checkWindowSizeStep(size, step) + if (this is RandomAccess && this is List) { + val thisSize = this.size + val result = ArrayList((thisSize + step - 1) / step) + val window = MovingSubList(this) + var index = 0 + while (index < thisSize) { + window.move(index, (index + size).coerceAtMost(thisSize)) + result.add(transform(window)) + index += step + } + return result } - return windowForwardOnlySequenceImpl(iterator(), size, step, dropTrailing = false).asIterable().map(transform) + val result = ArrayList() + windowedIterator(iterator(), size, step, dropTrailing = false, reuseBuffer = true).forEach { + result.add(transform(it)) + } + return result """ } customSignature(CharSequences) { "windowed(size: Int, step: Int, transform: (CharSequence) -> R)" } body(CharSequences) { """ - return windowIndices(this.length, size, step, dropTrailing = false).asIterable().map { transform(subSequence(it)) } + checkWindowSizeStep(size, step) + val thisSize = this.length + val result = ArrayList((thisSize + step - 1) / step) + var index = 0 + while (index < thisSize) { + result.add(transform(subSequence(index, (index + size).coerceAtMost(thisSize)))) + index += step + } + return result """ } 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) + return windowedSequence(size, step, dropTrailing = false, reuseBuffer = true).map(transform) """ } } @@ -598,8 +619,33 @@ fun generators(): List { returns(Sequences) { "Sequence>" } returns(CharSequences) { "List" } - body { "return windowed(size, step) { it.toList() }" } + + body { + """ + checkWindowSizeStep(size, step) + if (this is RandomAccess && this is List) { + val thisSize = this.size + val result = ArrayList>((thisSize + step - 1) / step) + var index = 0 + while (index < thisSize) { + result.add(List(size.coerceAtMost(thisSize - index)) { this[it + index] }) + index += step + } + return result + } + val result = ArrayList>() + windowedIterator(iterator(), size, step, dropTrailing = false, reuseBuffer = false).forEach { + result.add(it) + } + return result + """ + } body(CharSequences) { "return windowed(size, step) { it.toString() }" } + body(Sequences) { + """ + return windowedSequence(size, step, dropTrailing = false, reuseBuffer = false) + """ + } } templates add f("windowedSequence(size: Int, step: Int, transform: (CharSequence) -> R)") { @@ -608,9 +654,10 @@ fun generators(): List { typeParam("R") returns { "Sequence "} - body(CharSequences) { + body { """ - return windowIndices(this.length, size, step, dropTrailing = false).map { transform(subSequence(it)) } + checkWindowSizeStep(size, step) + return (indices step step).asSequence().map { index -> transform(subSequence(index, (index + size).coerceAtMost(length))) } """ } } @@ -643,8 +690,7 @@ fun generators(): List { returns(Sequences) { "Sequence>" } returns(CharSequences) { "List" } - body { "return chunked(size) { it.toList() }" } - body(CharSequences) { "return chunked(size) { it.toString() }" } + body { "return windowed(size, size)" } } templates add f("chunkedSequence(size: Int, transform: (CharSequence) -> R)") { @@ -653,7 +699,11 @@ fun generators(): List { typeParam("R") returns { "Sequence "} - body { "return windowedSequence(size, size, transform)" } + body { + """ + return windowedSequence(size, size, transform) + """ + } } templates add f("chunkedSequence(size: Int)") {