CollectionsKt.windowed throws IllegalArgumentException

This commit is contained in:
Abduqodiri Qurbonzoda
2019-09-16 20:34:13 +03:00
parent a7b984bcbf
commit d66f07a84e
6 changed files with 51 additions and 8 deletions
@@ -2147,7 +2147,8 @@ public fun <T> Iterable<T>.windowed(size: Int, step: Int = 1, partialWindows: Bo
checkWindowSizeStep(size, step) checkWindowSizeStep(size, step)
if (this is RandomAccess && this is List) { if (this is RandomAccess && this is List) {
val thisSize = this.size val thisSize = this.size
val result = ArrayList<List<T>>((thisSize + step - 1) / step) val resultCapacity = thisSize / step + if (thisSize % step == 0) 0 else 1
val result = ArrayList<List<T>>(resultCapacity)
var index = 0 var index = 0
while (index < thisSize) { while (index < thisSize) {
val windowSize = size.coerceAtMost(thisSize - index) val windowSize = size.coerceAtMost(thisSize - index)
@@ -1374,7 +1374,8 @@ public fun CharSequence.windowed(size: Int, step: Int = 1, partialWindows: Boole
public fun <R> CharSequence.windowed(size: Int, step: Int = 1, partialWindows: Boolean = false, transform: (CharSequence) -> R): List<R> { public fun <R> CharSequence.windowed(size: Int, step: Int = 1, partialWindows: Boolean = false, transform: (CharSequence) -> R): List<R> {
checkWindowSizeStep(size, step) checkWindowSizeStep(size, step)
val thisSize = this.length val thisSize = this.length
val result = ArrayList<R>((thisSize + step - 1) / step) val resultCapacity = thisSize / step + if (thisSize % step == 0) 0 else 1
val result = ArrayList<R>(resultCapacity)
var index = 0 var index = 0
while (index < thisSize) { while (index < thisSize) {
val end = index + size val end = index + size
@@ -22,9 +22,10 @@ internal fun <T> Sequence<T>.windowedSequence(size: Int, step: Int, partialWindo
internal fun <T> windowedIterator(iterator: Iterator<T>, size: Int, step: Int, partialWindows: Boolean, reuseBuffer: Boolean): Iterator<List<T>> { internal fun <T> windowedIterator(iterator: Iterator<T>, size: Int, step: Int, partialWindows: Boolean, reuseBuffer: Boolean): Iterator<List<T>> {
if (!iterator.hasNext()) return EmptyIterator if (!iterator.hasNext()) return EmptyIterator
return iterator<List<T>> { return iterator<List<T>> {
val bufferInitialCapacity = size.coerceAtMost(1024)
val gap = step - size val gap = step - size
if (gap >= 0) { if (gap >= 0) {
var buffer = ArrayList<T>(size) var buffer = ArrayList<T>(bufferInitialCapacity)
var skip = 0 var skip = 0
for (e in iterator) { for (e in iterator) {
if (skip > 0) { skip -= 1; continue } if (skip > 0) { skip -= 1; continue }
@@ -39,10 +40,12 @@ internal fun <T> windowedIterator(iterator: Iterator<T>, size: Int, step: Int, p
if (partialWindows || buffer.size == size) yield(buffer) if (partialWindows || buffer.size == size) yield(buffer)
} }
} else { } else {
val buffer = RingBuffer<T>(size) var buffer = RingBuffer<T>(bufferInitialCapacity)
for (e in iterator) { for (e in iterator) {
buffer.add(e) buffer.add(e)
if (buffer.isFull()) { if (buffer.isFull()) {
if (buffer.size < size) { buffer = buffer.expanded(maxCapacity = size); continue }
yield(if (reuseBuffer) buffer else ArrayList(buffer)) yield(if (reuseBuffer) buffer else ArrayList(buffer))
buffer.removeFirst(step) buffer.removeFirst(step)
} }
@@ -83,15 +86,18 @@ internal class MovingSubList<out E>(private val list: List<E>) : AbstractList<E>
* *
* Buffer overflow is not allowed so [add] doesn't overwrite tail but raises an exception. * Buffer overflow is not allowed so [add] doesn't overwrite tail but raises an exception.
*/ */
private class RingBuffer<T>(val capacity: Int) : AbstractList<T>(), RandomAccess { private class RingBuffer<T>(private val buffer: Array<Any?>, filledSize: Int) : AbstractList<T>(), RandomAccess {
init { init {
require(capacity >= 0) { "ring buffer capacity should not be negative but it is $capacity" } require(filledSize >= 0) { "ring buffer filled size should not be negative but it is $filledSize" }
require(filledSize <= buffer.size) { "ring buffer filled size: $filledSize cannot be larger than the buffer size: ${buffer.size}" }
} }
private val buffer = arrayOfNulls<Any?>(capacity) constructor(capacity: Int) : this(arrayOfNulls<Any?>(capacity), 0)
private val capacity = buffer.size
private var startIndex: Int = 0 private var startIndex: Int = 0
override var size: Int = 0 override var size: Int = filledSize
private set private set
override fun get(index: Int): T { override fun get(index: Int): T {
@@ -149,6 +155,16 @@ private class RingBuffer<T>(val capacity: Int) : AbstractList<T>(), RandomAccess
return toArray(arrayOfNulls(size)) return toArray(arrayOfNulls(size))
} }
/**
* Creates a new ring buffer with the capacity equal to the minimum of [maxCapacity] and 1.5 * [capacity].
* The returned ring buffer contains the same elements as this ring buffer.
*/
fun expanded(maxCapacity: Int): RingBuffer<T> {
val newCapacity = (capacity + (capacity shr 1) + 1).coerceAtMost(maxCapacity)
val newBuffer = if (startIndex == 0) buffer.copyOf(newCapacity) else toArray(arrayOfNulls(newCapacity))
return RingBuffer(newBuffer, size)
}
/** /**
* Add [element] to the buffer or fail with [IllegalStateException] if no free space available in the buffer * Add [element] to the buffer or fail with [IllegalStateException] if no free space available in the buffer
*/ */
@@ -140,6 +140,11 @@ abstract class OrderedIterableTests<T : Iterable<String>>(createFrom: (Array<out
data.toList().let { expectedSingleChunk -> data.toList().let { expectedSingleChunk ->
assertEquals(expectedSingleChunk, data.chunked(size).single()) assertEquals(expectedSingleChunk, data.chunked(size).single())
assertEquals(expectedSingleChunk, data.chunked(size + 3).single()) assertEquals(expectedSingleChunk, data.chunked(size + 3).single())
assertEquals(expectedSingleChunk, data.chunked(Int.MAX_VALUE).single())
}
createFrom("a", "b").let { iterable ->
assertEquals(iterable.toList(), iterable.chunked(Int.MAX_VALUE).single())
} }
assertTrue(empty.chunked(3).isEmpty()) assertTrue(empty.chunked(3).isEmpty())
@@ -230,6 +230,11 @@ public class SequenceTest {
seq.toList().let { expectedSingleChunk -> seq.toList().let { expectedSingleChunk ->
assertEquals(expectedSingleChunk, seq.chunked(size).single()) assertEquals(expectedSingleChunk, seq.chunked(size).single())
assertEquals(expectedSingleChunk, seq.chunked(size + 3).single()) assertEquals(expectedSingleChunk, seq.chunked(size + 3).single())
assertEquals(expectedSingleChunk, seq.chunked(Int.MAX_VALUE).single())
}
infiniteSeq.take(2).let { seq2 ->
assertEquals(seq2.toList(), seq2.chunked(Int.MAX_VALUE).single())
} }
assertTrue(emptySequence<String>().chunked(3).none()) assertTrue(emptySequence<String>().chunked(3).none())
@@ -251,6 +256,16 @@ public class SequenceTest {
assertEquals((startElement until startElement + 5).toList(), window) assertEquals((startElement until startElement + 5).toList(), window)
} }
infiniteSeq.take(3500).windowed(2000, 1000, partialWindows = true).forEachIndexed { windowIndex, window ->
val startElement = windowIndex * 1000
val elementCount = when (windowIndex) {
3 -> 500
2 -> 1500
else -> 2000
}
assertEquals((startElement until startElement + elementCount).toList(), window)
}
val size = 7 val size = 7
val seq = infiniteSeq.take(7) val seq = infiniteSeq.take(7)
+5
View File
@@ -1068,6 +1068,11 @@ class StringTest {
data.toString().let { expectedSingleChunk -> data.toString().let { expectedSingleChunk ->
assertEquals(expectedSingleChunk, data.chunked(size).single()) assertEquals(expectedSingleChunk, data.chunked(size).single())
assertEquals(expectedSingleChunk, data.chunked(size + 3).single()) assertEquals(expectedSingleChunk, data.chunked(size + 3).single())
assertEquals(expectedSingleChunk, data.chunked(Int.MAX_VALUE).single())
}
arg1("ab").let {
assertEquals(it.toString(), it.chunked(Int.MAX_VALUE).single())
} }
assertTrue(arg1("").chunked(3).isEmpty()) assertTrue(arg1("").chunked(3).isEmpty())