Provide constrainOnce extension method for the Sequence.

Sequence created from the generator function without an initial value can be iterated only once.
The same is for BufferedReader.lines sequence.
This commit is contained in:
Ilya Gorbunov
2015-04-02 22:16:51 +03:00
parent 698ffa2807
commit f2d865e836
5 changed files with 106 additions and 29 deletions
@@ -280,8 +280,7 @@ public class TakeSequence<T>(private val sequence: Sequence<T>,
private val count: Int
) : Sequence<T> {
init {
if (count < 0)
throw IllegalArgumentException("count should be non-negative, but is $count")
require (count >= 0) { throw IllegalArgumentException("count should be non-negative, but is $count") }
}
override fun iterator(): Iterator<T> = object : Iterator<T> {
@@ -361,8 +360,7 @@ public class DropSequence<T>(private val sequence: Sequence<T>,
private val count: Int
) : Sequence<T> {
init {
if (count < 0)
throw IllegalArgumentException("count should be non-negative, but is $count")
require (count >= 0) { throw IllegalArgumentException("count should be non-negative, but is $count") }
}
override fun iterator(): Iterator<T> = object : Iterator<T> {
@@ -510,11 +508,26 @@ private class GeneratorSequence<T: Any>(private val getInitialValue: () -> T?, p
}
}
/**
* Returns a wrapper sequence that provides values of this sequence, but ensures it can be iterated only one time.
*
* [IllegalStateException] is thrown on iterating the returned sequence from the second time.
*/
public fun <T> Sequence<T>.constrainOnce(): Sequence<T> {
// as? does not work in js
//return this as? ConstrainedOnceSequence<T> ?: ConstrainedOnceSequence(this)
return if (this is ConstrainedOnceSequence<T>) this else ConstrainedOnceSequence(this)
}
/**
* Returns a sequence which invokes the function to calculate the next value on each iteration until the function returns `null`.
*
* Returned sequence is constrained to be iterated only once.
*
* @see constrainOnce
*/
public fun <T : Any> sequence(nextFunction: () -> T?): Sequence<T> {
return GeneratorSequence(nextFunction, { nextFunction() })
return GeneratorSequence(nextFunction, { nextFunction() }).constrainOnce()
}
deprecated("Use sequence() instead")
@@ -0,0 +1,15 @@
package kotlin
import java.util.concurrent.atomic.AtomicReference
private class ConstrainedOnceSequence<T>(sequence: Sequence<T>) : Sequence<T> {
private val sequenceRef = AtomicReference(sequence)
override fun iterator(): Iterator<T> {
val sequence = sequenceRef.getAndSet(null) ?: throw IllegalStateException("This sequence can be consumed only once.")
return sequence.iterator()
}
}
+2 -2
View File
@@ -246,9 +246,9 @@ public inline fun <T> Reader.useLines(block: (Sequence<String>) -> T): T =
*
* We suggest you try the method [useLines] instead which closes the stream when the processing is complete.
*
* @return a sequence of corresponding file lines
* @return a sequence of corresponding file lines. The sequence returned can be iterated only once.
*/
public fun BufferedReader.lines(): Sequence<String> = LinesSequence(this)
public fun BufferedReader.lines(): Sequence<String> = LinesSequence(this).constrainOnce()
deprecated("Use lines() function which returns Sequence<String>")
public fun BufferedReader.lineIterator(): Iterator<String> = lines().iterator()
@@ -20,24 +20,24 @@ fun fibonacci(): Sequence<Int> {
public class SequenceTest {
test fun filterEmptySequence() {
val stream = sequenceOf<String>()
assertEquals(0, stream.filter { false }.count())
assertEquals(0, stream.filter { true }.count())
val sequence = sequenceOf<String>()
assertEquals(0, sequence.filter { false }.count())
assertEquals(0, sequence.filter { true }.count())
}
test fun mapEmptyStream() {
val stream = sequenceOf<String>()
assertEquals(0, stream.map { false }.count())
assertEquals(0, stream.map { true }.count())
test fun mapEmptySequence() {
val sequence = sequenceOf<String>()
assertEquals(0, sequence.map { false }.count())
assertEquals(0, sequence.map { true }.count())
}
test fun requireNoNulls() {
val stream = sequenceOf<String?>("foo", "bar")
val notNull = stream.requireNoNulls()
val sequence = sequenceOf<String?>("foo", "bar")
val notNull = sequence.requireNoNulls()
assertEquals(listOf("foo", "bar"), notNull.toList())
val streamWithNulls = sequenceOf("foo", null, "bar")
val notNull2 = streamWithNulls.requireNoNulls() // shouldn't fail yet
val sequenceWithNulls = sequenceOf("foo", null, "bar")
val notNull2 = sequenceWithNulls.requireNoNulls() // shouldn't fail yet
fails {
// should throw an exception as we have a null
notNull2.toList()
@@ -128,9 +128,9 @@ public class SequenceTest {
}
test fun plus() {
val stream = sequenceOf("foo", "bar")
val streamCheese = stream + "cheese"
assertEquals(listOf("foo", "bar", "cheese"), streamCheese.toList())
val sequence = sequenceOf("foo", "bar")
val sequenceCheese = sequence + "cheese"
assertEquals(listOf("foo", "bar", "cheese"), sequenceCheese.toList())
// lets use a mutable variable
var mi = sequenceOf("a", "b")
@@ -141,8 +141,8 @@ public class SequenceTest {
test fun plusCollection() {
val a = sequenceOf("foo", "bar")
val b = listOf("cheese", "wine")
val stream = a + b
assertEquals(listOf("foo", "bar", "cheese", "wine"), stream.toList())
val sequence = a + b
assertEquals(listOf("foo", "bar", "cheese", "wine"), sequence.toList())
// lets use a mutable variable
var ml = sequenceOf("a")
@@ -154,7 +154,7 @@ public class SequenceTest {
}
test fun iterationOverStream() {
test fun iterationOverSequence() {
var s = ""
for (i in sequenceOf(0, 1, 2, 3, 4, 5)) {
s = s + i.toString()
@@ -162,31 +162,51 @@ public class SequenceTest {
assertEquals("012345", s)
}
test fun streamFromFunction() {
test fun sequenceFromFunction() {
var count = 3
val stream = sequence {
val sequence = sequence {
count--
if (count >= 0) count else null
}
val list = stream.toList()
val list = sequence.toList()
assertEquals(listOf(2, 1, 0), list)
fails {
sequence.toList()
}
}
test fun streamFromFunctionWithInitialValue() {
test fun sequenceFromFunctionWithInitialValue() {
val values = sequence(3) { n -> if (n > 0) n - 1 else null }
val expected = listOf(3, 2, 1, 0)
assertEquals(expected, values.toList())
assertEquals(expected, values.toList(), "Iterating sequence second time yields the same result")
}
test fun makeSequenceOneTimeConstrained() {
val sequence = sequenceOf(1, 2, 3, 4)
sequence.toList()
sequence.toList()
val oneTime = sequence.constrainOnce()
oneTime.toList()
assertTrue("should fail with IllegalStateException") {
fails {
oneTime.toList()
} is IllegalStateException
}
}
private fun <T, C : MutableCollection<in T>> Sequence<T>.takeWhileTo(result: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) result.add(element) else break
return result
}
test fun streamExtensions() {
test fun sequenceExtensions() {
val d = ArrayList<Int>()
sequenceOf(0, 1, 2, 3, 4, 5).takeWhileTo(d, { i -> i < 4 })
assertEquals(4, d.size())