diff --git a/libraries/stdlib/src/kotlin/Iterators.kt b/libraries/stdlib/src/kotlin/Iterators.kt index 8ff451c349a..9074972a0f8 100644 --- a/libraries/stdlib/src/kotlin/Iterators.kt +++ b/libraries/stdlib/src/kotlin/Iterators.kt @@ -3,61 +3,57 @@ package kotlin import kotlin.support.AbstractIterator import kotlin.support.FunctionIterator -import java.util.* -import java.util.Iterator +/** + * Returns an iterator which invokes the function to calculate the next value on each iteration until the function returns *null* + * + * @includeFunction ../../test/iterators/IteratorsTest.kt fibonacci + */ +inline fun iterate(nextFunction: () -> T?) : java.util.Iterator = FunctionIterator(nextFunction) -/** Filters the iterator for all elements of a certain sub class */ -inline fun java.util.Iterator.filterIsInstance(klass: Class): Iterator = FilterIsIterator(this, klass) +/** Returns an iterator over elements that are instances of a given type *R* which is a subclass of *T* */ +inline fun java.util.Iterator.filterIsInstance(klass: Class): java.util.Iterator = FilterIsIterator(this, klass) -private class FilterIsIterator(val iter: java.util.Iterator, val klass: Class) : AbstractIterator() { +private class FilterIsIterator(val iterator : java.util.Iterator, val klass: Class) : AbstractIterator() { override protected fun computeNext(): R? { - while (iter.hasNext()) { - val next = iter.next() - if (klass.isInstance(next)) { - return next as R - } + while (iterator.hasNext()) { + val next = iterator.next() + if (klass.isInstance(next)) return next as R } done() return null } } -/** Returns a new lazy iterator containing all elements in this iteration which match the given predicate */ -inline fun java.util.Iterator.filter(predicate: (T)-> Boolean) : java.util.Iterator { - return FilterIterator(this, predicate) -} +/** + * Returns an iterator over elements which match the given *predicate* + * + * @includeFunction ../../test/iterators/IteratorsTest.kt filterAndTakeWhileExtractTheElementsWithinRange + */ +inline fun java.util.Iterator.filter(predicate: (T) -> Boolean) : java.util.Iterator = FilterIterator(this, predicate) -private class FilterIterator(val iter: java.util.Iterator, val predicate: (T)-> Boolean) : AbstractIterator() { +private class FilterIterator(val iterator : java.util.Iterator, val predicate: (T)-> Boolean) : AbstractIterator() { override protected fun computeNext(): T? { - while (iter.hasNext()) { - val element = iter.next() - if ((predicate)(element)) { - return element - } + while (iterator.hasNext()) { + val next = iterator.next() + if ((predicate)(next)) return next } done() return null } } -/** Returns a new lazy iterator containing all elements in this iteration which do not match the given predicate */ -inline fun java.util.Iterator.filterNot(predicate: (T)-> Boolean) : java.util.Iterator { - return filter { - !predicate(it) - } -} +/** Returns an iterator over elements which do not match the given *predicate* */ +inline fun java.util.Iterator.filterNot(predicate: (T) -> Boolean) : java.util.Iterator = filter { !predicate(it) } -/** Returns a new lazy iterator containing all the non null elements in this iteration */ +/** Returns an iterator over non-*null* elements */ inline fun java.util.Iterator?.filterNotNull() : java.util.Iterator = FilterNotNullIterator(this) -private class FilterNotNullIterator(val iter: java.util.Iterator?) : AbstractIterator() { - override fun computeNext(): T? { - if (iter != null) { - while (iter.hasNext()) { - val next = iter.next() - if (next != null) { - return next - } +private class FilterNotNullIterator(val iterator : java.util.Iterator?) : AbstractIterator() { + override protected fun computeNext(): T? { + if (iterator != null) { + while (iterator.hasNext()) { + val next = iterator.next() + if (next != null) return next } } done() @@ -65,21 +61,88 @@ private class FilterNotNullIterator(val iter: java.util.Iterator?) : Abst } } - /** - * Returns a lazy iterator containing the result of transforming each item in the iteration to a one or more values which - * are concatenated together into a single collection - */ -/* - - TODO implement a lazy flatMap - -inline fun java.util.Iterator.flatMap(transform: (T)-> java.util.Iterator) : Collection { - return flatMapTo<>(ArrayList(), transform) -} -*/ - -/** - * Returns an iterator which invokes the function to calculate the next value on each iteration until the function returns null + * Returns an iterator obtained by applying *transform*, a function transforming an object of type *T* into an object of type *R* + * + * @includeFunction ../../test/iterators/IteratorsTest.kt mapAndTakeWhileExtractTheTransformedElements */ -inline fun iterate(nextFunction: () -> T?) : Iterator = FunctionIterator(nextFunction) \ No newline at end of file +inline fun java.util.Iterator.map(transform: (T) -> R): java.util.Iterator = MapIterator(this, transform) + +private class MapIterator(val iterator : java.util.Iterator, val transform: (T) -> R) : AbstractIterator() { + override protected fun computeNext() : R? = if (iterator.hasNext()) (transform)(iterator.next()) else { done(); null } +} + +/** + * Returns an iterator over the concatenated results of transforming each element to one or more values + * + * @includeFunction ../../test/iterators/IteratorsTest.kt flatMapAndTakeExtractTheTransformedElements + */ +inline fun java.util.Iterator.flatMap(transform: (T) -> java.util.Iterator): java.util.Iterator = FlatMapIterator(this, transform) + +private class FlatMapIterator(val iterator : java.util.Iterator, val transform: (T) -> java.util.Iterator) : AbstractIterator() { + var transformed: java.util.Iterator = iterate { null } + + override protected fun computeNext() : R? { + if (transformed.hasNext()) return transformed.next() + if (iterator.hasNext()) { + transformed = (transform)(iterator.next()) + return computeNext() + } + done() + return null + } +} + +/** + * Returns an iterator restricted to the first *n* elements + * + * @includeFunction ../../test/iterators/IteratorsTest.kt takeExtractsTheFirstNElements + */ +inline fun java.util.Iterator.take(n: Int): java.util.Iterator { + fun countTo(n: Int): (T) -> Boolean { + var count = 0 + return { ++count; count <= n } + } + return takeWhile(countTo(n)) +} + +/** + * Returns an iterator restricted to the first elements that match the given *predicate* + * + * @includeFunction ../../test/iterators/IteratorsTest.kt filterAndTakeWhileExtractTheElementsWithinRange + */ +inline fun java.util.Iterator.takeWhile(predicate: (T) -> Boolean): java.util.Iterator = TakeWhileIterator(this, predicate) + +private class TakeWhileIterator(val iterator: java.util.Iterator, val predicate: (T) -> Boolean) : AbstractIterator() { + override protected fun computeNext() : T? { + if (iterator.hasNext()) { + val item = iterator.next() + if ((predicate)(item)) return item + } + done() + return null + } +} + +/** + * Creates a string from the first *n (= limit)* elements separated using the *separator* and using the given *prefix* and *postfix* if supplied + * + * @includeFunction ../../test/iterators/IteratorsTest.kt joinConcatenatesTheFirstNElementsAboveAThreshold + */ +inline fun java.util.Iterator.join(separator: String, prefix: String = "", postfix: String = "", limit: Int = 20) : String { + val buffer = StringBuilder(prefix) + var first = true; var count = 0 + for (element in this) { + if (first) first = false else buffer.append(separator) + if (++count <= limit) buffer.append(element) else break + } + if (count > limit) buffer.append("...") + return buffer.append(postfix).toString().sure() +} + +/** + * Returns a comma-separated representation of no more than the first 10 elements + * + * @includeFunction ../../test/iterators/IteratorsTest.kt toStringJoinsNoMoreThanTheFirstTenElements + */ +inline fun java.util.Iterator.toString(): String = join(separator = ", ", limit = 10) diff --git a/libraries/stdlib/test/iterators/IteratorsTest.kt b/libraries/stdlib/test/iterators/IteratorsTest.kt new file mode 100644 index 00000000000..caf00e89626 --- /dev/null +++ b/libraries/stdlib/test/iterators/IteratorsTest.kt @@ -0,0 +1,59 @@ +package iterators + +import kotlin.test.assertEquals +import org.junit.Test + +class IteratorsTest { + + private fun fibonacci(): java.util.Iterator { + // fibonacci terms + var index = 0; var a = 0; var b = 1 + return iterate { when (index++) { 0 -> a; 1 -> b; else -> { val result = a + b; a = b; b = result; result } } } + } + + Test fun filterAndTakeWhileExtractTheElementsWithinRange() { + assertEquals(arrayList(144, 233, 377, 610, 987), fibonacci().filter { it > 100 }.takeWhile { it < 1000 }.toList()) + } + + Test fun foldReducesTheFirstNElements() { + val sum = { (a: Int, b: Int) -> a + b } + assertEquals(arrayList(13, 21, 34, 55, 89).fold(0, sum), fibonacci().filter { it > 10 }.take(5).fold(0, sum)) + } + + Test fun takeExtractsTheFirstNElements() { + assertEquals(arrayList(0, 1, 1, 2, 3, 5, 8, 13, 21, 34), fibonacci().take(10).toList()) + } + + Test fun mapAndTakeWhileExtractTheTransformedElements() { + assertEquals(arrayList(0, 3, 3, 6, 9, 15), fibonacci().map { it * 3 }.takeWhile { (i: Int) -> i < 20 }.toList()) + } + + Test fun flatMapAndTakeExtractTheTransformedElements() { + fun intToBinaryDigits() = { (i: Int) -> + val binary = Integer.toBinaryString(i).sure() + var index = 0 + iterate { if (index < binary.length()) binary.get(index++) else null } + } + + val expected = arrayList( + '0', // fibonacci(0) = 0 + '1', // fibonacci(1) = 1 + '1', // fibonacci(2) = 1 + '1', '0', // fibonacci(3) = 2 + '1', '1', // fibonacci(4) = 3 + '1', '0', '1' // fibonacci(5) = 5 + ) + + assertEquals(expected, fibonacci().flatMap(intToBinaryDigits()).take(10).toList()) + } + + Test fun joinConcatenatesTheFirstNElementsAboveAThreshold() { + assertEquals("13, 21, 34, 55, 89, ...", fibonacci().filter { it > 10 }.join(separator = ", ", limit = 5)) + } + + Test fun toStringJoinsNoMoreThanTheFirstTenElements() { + assertEquals("0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...", fibonacci().toString()) + assertEquals("13, 21, 34, 55, 89, 144, 233, 377, 610, 987, ...", fibonacci().filter { it > 10 }.toString()) + assertEquals("144, 233, 377, 610, 987", fibonacci().filter { it > 100 }.takeWhile { it < 1000 }.toString()) + } +}