diff --git a/libraries/stdlib/src/sequences/Sequence.kt b/libraries/stdlib/src/sequences/Sequence.kt new file mode 100644 index 00000000000..ce85477097e --- /dev/null +++ b/libraries/stdlib/src/sequences/Sequence.kt @@ -0,0 +1,105 @@ +package kotlin.sequences + +/** + * A sequence of items of type *T* that are lazily produced by the given closure + * + * @param next a closure yielding the next element in this sequence + * + * @author [Franck Rasolo](http://www.linkedin.com/in/franckrasolo) + * @since 1.0 + */ +class Sequence(val next: () -> T?) : Iterable { + + override fun iterator(): Iterator = YieldingIterator { (next)() } + + /** + * Retains only elements that satisfy the given predicate + * + * @param predicate the predicate evaluated against objects of type *T* + */ + fun filter(predicate: (T) -> Boolean): Sequence { + val iterator = iterator() + + fun next(): T? { + while (iterator.hasNext) { + val item = iterator.next() + if ((predicate)(item)) return item + } + return null + } + + return Sequence { next() } + } + + /** + * Reduces this sequence using the binary operator, from left to right. + * Only finite sequences can be reduced. + * + * @param seed an initial value, typically the left-identity of the operator + * @param operator a binary operator + */ + fun fold(seed: T, operator: (T, T) -> T): T { + var result = seed + for (item in this) result = (operator)(result, item) + return result + } + + /** + * Produces a sequence obtained by applying *transform* to each element of this sequence + * + * @param transform the function transforming an object of type *T* into an object of type *R* + */ + fun map(transform: (T) -> R): Sequence { + val iterator = iterator() + fun next(): R? = if (iterator.hasNext) (transform)(iterator.next()) else null + return Sequence { next() } + } + + /** + * Extracts the prefix of this sequence as a finite sequence of length *n* + * + * @param n the number of elements of the extracted sequence + */ + fun take(n: Int): Sequence { + fun countTo(n: Int): (T) -> Boolean { + var count = 0 + return { ++count; count <= n } + } + return takeWhile(countTo(n)) + } + + /** + * Extracts the longest prefix, possibly empty, of this sequence where each element satisfies *predicate* + * + * @param predicate the predicate evaluated against objects of type *T* + */ + fun takeWhile(predicate: (T) -> Boolean): Sequence { + val iterator = iterator() + + fun next(): T? { + if (iterator.hasNext) { + val item = iterator.next() + if ((predicate)(item)) return item + } + return null + } + + return Sequence { next() } + } +} + +private class YieldingIterator(val yield: () -> T?) : Iterator { + var current : T? = (yield)() + + override val hasNext: Boolean + get() = current != null + + override fun next(): T { + val next = current + if (next != null) { + current = (yield)() + return next + } + throw java.util.NoSuchElementException() + } +} diff --git a/libraries/stdlib/src/sequences/Sequences.kt b/libraries/stdlib/src/sequences/Sequences.kt new file mode 100644 index 00000000000..2a098729179 --- /dev/null +++ b/libraries/stdlib/src/sequences/Sequences.kt @@ -0,0 +1,74 @@ +package kotlin.sequences + +/** Creates a sequence from the given list of *elements* */ +inline fun sequence(vararg elements: T): Sequence = asSequence(elements.iterator()) + +/** Creates a sequence of elements lazily obtained from this Kotlin [[jet.Iterable]] */ +inline fun Iterable.asSequence(): Sequence = asSequence(iterator()) + +/** Creates a sequence of elements lazily obtained from this Java [[java.lang.Iterable]] */ +inline fun java.lang.Iterable.asSequence(): Sequence = asSequence(iterator().sure() as java.util.Iterator) + +private fun asSequence(iterator: Iterator) = Sequence { if (iterator.hasNext) iterator.next() else null } +private fun asSequence(iterator: java.util.Iterator) = Sequence { if (iterator.hasNext()) iterator.next() else null } + +/** + * Produces the [cartesian square](http://en.wikipedia.org/wiki/Cartesian_product#Cartesian_square_and_Cartesian_power) + * as sequence of order pairs of elements lazily obtained from this range of elements + */ +fun Iterable.cartesianSquare(): Sequence<#(T, T)> { + val first = iterator(); var second = iterator(); var a: T? = null + + fun nextPair(): #(T, T)? { + if (a == null && first.hasNext) a = first.next() + if (second.hasNext) return #(a.sure(), second.next()) + if (first.hasNext) { + a = first.next(); second = iterator() + return #(a.sure(), second.next()) + } + return null + } + + return Sequence<#(T, T)> { nextPair() } +} + +/** + * Partitions this string into groups of fixed size strings + * + * @param size the number of characters per group + * + * @return a sequence of strings of size *size*, except the last will be truncated if the elements do not divide evenly + */ +fun String.grouped(size: Int, iterator: CharIterator = iterator()): Sequence { + fun nextGroup(): String? { + if (iterator.hasNext) { + val window = StringBuilder() + for (i in 1..size) if (iterator.hasNext) window.append(iterator.next()) + return window.toString() + } + return null + } + + return Sequence { nextGroup() } +} + +/** + * Groups elements in fixed size blocks by passing a *sliding window* over them, as opposed to partitioning them, as is done in `String.grouped` + * + * @param size the number of characters per group + * + * @return a sequence of strings of size *size*, except the last and the only element will be truncated if there are fewer characters than *size* + */ +fun String.sliding(size: Int, iterator: CharIterator = iterator()): Sequence { + val window = StringBuilder() + + fun nextWindow(): String? { + if (window.length() == 0) { + for (i in 1..size) if (iterator.hasNext) window.append(iterator.next()) + return window.toString() + } + return if (iterator.hasNext) window.deleteCharAt(0)?.append(iterator.next()).toString() else null + } + + return Sequence { nextWindow() } +} diff --git a/libraries/stdlib/test/sequences/SequenceTest.kt b/libraries/stdlib/test/sequences/SequenceTest.kt new file mode 100644 index 00000000000..1e53f7c28c1 --- /dev/null +++ b/libraries/stdlib/test/sequences/SequenceTest.kt @@ -0,0 +1,83 @@ +package test.sequences + +import java.util.ArrayList + +import kotlin.sequences.asSequence +import kotlin.sequences.sequence +import kotlin.util.arrayList +import kotlin.util.fold + +import kotlin.test.assertEquals +import org.junit.Test + +class SequenceTest { + + Test fun filterReturnsTheExpectedSequence() { + val actual = (1..10).asSequence().filter { it % 2 == 0 }.toList() + assertEquals(arrayList(2, 4, 6, 8, 10), actual) + } + + Test fun filterReturnsAnEmptySequenceWhenNoElementMatchesThePredicate() { + val actual = sequence(1, 3, 4, 7).filter { it > 7 }.toList() + assertEquals(ArrayList(), actual) + } + + Test fun foldReturnsTheExpectedReduction() { + val actual = (1..10).asSequence().filter { it % 2 == 1 }.fold(0, sum) + assertEquals(1 + 3 + 5 + 7 + 9, actual) + } + + Test fun foldReturnsTheInitialValueForAnEmptySequence() { + val initialValue = 0 + val actual = (1..0).asSequence().fold(initialValue, sum) + + assertEquals(initialValue, actual) + } + + Test fun mapReturnsTheExpectedTransformation() { + val actual = sequence(1, 3, 4, 7).map { it * 2 }.toList() + assertEquals(arrayList(2, 6, 8, 14), actual) + } + + Test fun takeReturnsTheFirstNElements() { + var actual = (1..5).asSequence().take(3).toList() + assertEquals(arrayList(1, 2, 3), actual) + } + + Test fun takeReturnsTheEntireSequenceWhenTheNumberOfElementsIsGreaterThanItsSize() { + val actual = sequence(1, 2).take(3).toList() + assertEquals(arrayList(1, 2), actual) + } + + Test fun takeReturnsAnEmptySequenceForAnEmptySequence() { + val actual = (1..0).asSequence().take(3).toList() + assertEquals(ArrayList(), actual) + } + + Test fun takeReturnsAnEmptySequenceWhenTheNumberOfElementsToExtractIsZero() { + val actual = sequence(1, 2).take(0).toList() + assertEquals(ArrayList(), actual) + } + + Test fun takeReturnsAnEmptySequenceWhenTheNumberOfElementsToExtractIsNegative() { + val actual = sequence(1, 2).take(-1).toList() + assertEquals(ArrayList(), actual) + } + + Test fun takeWhileReturnsTheFirstElementsMatchingAGivenPredicate() { + val actual = sequence(1, 2, 3, 4, 1, 2, 3, 4).takeWhile { it < 3 }.toList() + assertEquals(arrayList(1, 2), actual) + } + + Test fun takeWhileReturnsTheEntireSequenceWhenThePredicateMatchesAllElements() { + val actual = sequence(1, 2, 3).takeWhile { it < 9 }.toList() + assertEquals(arrayList(1, 2, 3), actual) + } + + Test fun takeWhileReturnsAnEmptySequenceWhenThePredicateMatchesNothing() { + val actual = sequence(1, 2, 3).takeWhile { it < 0 }.toList() + assertEquals(ArrayList(), actual) + } + + private val sum = { (a : Int, b : Int) -> a + b } +}