From 80041e6559009f2dfcec10d2f2f24a82be50f2f3 Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Mon, 20 Mar 2017 11:52:52 +0300 Subject: [PATCH] Sequences support improved (#358) --- runtime/src/main/kotlin/kotlin/Arrays.kt | 260 ++++++ .../main/kotlin/kotlin/collections/Arrays.kt | 43 + .../kotlin/kotlin/collections/Grouping.kt | 236 ++++++ .../main/kotlin/kotlin/collections/Maps.kt | 8 + .../main/kotlin/kotlin/sequences/Sequence.kt | 19 - .../main/kotlin/kotlin/sequences/Sequences.kt | 754 +++++++++++++++--- 6 files changed, 1181 insertions(+), 139 deletions(-) create mode 100644 runtime/src/main/kotlin/kotlin/collections/Arrays.kt create mode 100644 runtime/src/main/kotlin/kotlin/collections/Grouping.kt diff --git a/runtime/src/main/kotlin/kotlin/Arrays.kt b/runtime/src/main/kotlin/kotlin/Arrays.kt index 10be86fed21..9778063c0dd 100644 --- a/runtime/src/main/kotlin/kotlin/Arrays.kt +++ b/runtime/src/main/kotlin/kotlin/Arrays.kt @@ -2462,3 +2462,263 @@ public fun CharArray.joinToString(separator: CharSequence = ", ", prefix: CharSe return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() } +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun Array.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun ByteArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun ShortArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun IntArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun LongArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun FloatArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun DoubleArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun BooleanArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates an [Iterable] instance that wraps the original array returning its elements when being iterated. + */ +public fun CharArray.asIterable(): Iterable { + if (isEmpty()) return emptyList() + return Iterable { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun Array.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun ByteArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun ShortArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun IntArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun LongArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun FloatArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun DoubleArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun BooleanArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Creates a [Sequence] instance that wraps the original array returning its elements when being iterated. + * + * @sample samples.collections.Sequences.Building.sequenceFromArray + */ +public fun CharArray.asSequence(): Sequence { + if (isEmpty()) return emptySequence() + return Sequence { this.iterator() } +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun Array.sumBy(selector: (T) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun ByteArray.sumBy(selector: (Byte) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun ShortArray.sumBy(selector: (Short) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun IntArray.sumBy(selector: (Int) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun LongArray.sumBy(selector: (Long) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun FloatArray.sumBy(selector: (Float) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun DoubleArray.sumBy(selector: (Double) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun BooleanArray.sumBy(selector: (Boolean) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} + +/** + * Returns the sum of all values produced by [selector] function applied to each element in the array. + */ +public inline fun CharArray.sumBy(selector: (Char) -> Int): Int { + var sum: Int = 0 + for (element in this) { + sum += selector(element) + } + return sum +} diff --git a/runtime/src/main/kotlin/kotlin/collections/Arrays.kt b/runtime/src/main/kotlin/kotlin/collections/Arrays.kt new file mode 100644 index 00000000000..34a5d403781 --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/collections/Arrays.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.collections + +/** + * Returns a single list of all elements from all arrays in the given array. + */ +public fun Array>.flatten(): List { + val result = ArrayList(sumBy { it.size }) + for (element in this) { + result.addAll(element) + } + return result +} + +/** + * Returns a pair of lists, where + * *first* list is built from the first values of each pair from this array, + * *second* list is built from the second values of each pair from this array. + */ +public fun Array>.unzip(): Pair, List> { + val listT = ArrayList(size) + val listR = ArrayList(size) + for (pair in this) { + listT.add(pair.first) + listR.add(pair.second) + } + return listT to listR +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/collections/Grouping.kt b/runtime/src/main/kotlin/kotlin/collections/Grouping.kt new file mode 100644 index 00000000000..96ab0aa5c1f --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/collections/Grouping.kt @@ -0,0 +1,236 @@ +package kotlin.collections + +/** + * Represents a source of elements with a [keyOf] function, which can be applied to each element to get its key. + * + * A [Grouping] structure serves as an intermediate step in group-and-fold operations: + * they group elements by their keys and then fold each group with some aggregating operation. + * + * It is created by attaching `keySelector: (T) -> K` function to a source of elements. + * To get an instance of [Grouping] use one of `groupingBy` extension functions: + * - [Iterable.groupingBy] + * - [Sequence.groupingBy] + * - [Array.groupingBy] + * - [CharSequence.groupingBy] + * + * For the list of group-and-fold operations available, see the [extension functions](#extension-functions) for `Grouping`. + */ +@SinceKotlin("1.1") +public interface Grouping { + /** Returns an [Iterator] over the elements of the source of this grouping. */ + fun sourceIterator(): Iterator + /** Extracts the key of an [element]. */ + fun keyOf(element: T): K +} + +/** + * Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially, + * passing the previously accumulated value and the current element as arguments, and stores the results in a new map. + * + * The key for each element is provided by the [Grouping.keyOf] function. + * + * @param operation function is invoked on each element with the following parameters: + * - `key`: the key of the group this element belongs to; + * - `accumulator`: the current value of the accumulator of the group, can be `null` if it's the first `element` encountered in the group; + * - `element`: the element from the source being aggregated; + * - `first`: indicates whether it's the first `element` encountered in the group. + * + * @return a [Map] associating the key of each group with the result of aggregation of the group elements. + */ +@SinceKotlin("1.1") +public inline fun Grouping.aggregate( + operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R +): Map { + return aggregateTo(mutableMapOf(), operation) +} + +/** + * Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially, + * passing the previously accumulated value and the current element as arguments, + * and stores the results in the given [destination] map. + * + * The key for each element is provided by the [Grouping.keyOf] function. + * + * @param operation a function that is invoked on each element with the following parameters: + * - `key`: the key of the group this element belongs to; + * - `accumulator`: the current value of the accumulator of the group, can be `null` if it's the first `element` encountered in the group; + * - `element`: the element from the source being aggregated; + * - `first`: indicates whether it's the first `element` encountered in the group. + * + * If the [destination] map already has a value corresponding to some key, + * then the elements being aggregated for that key are never considered as `first`. + * + * @return the [destination] map associating the key of each group with the result of aggregation of the group elements. + */ +@SinceKotlin("1.1") +public inline fun > Grouping.aggregateTo( + destination: M, + operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R +): M { + for (e in this.sourceIterator()) { + val key = keyOf(e) + val accumulator = destination[key] + destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key)) + } + return destination +} + +/** + * Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially, + * passing the previously accumulated value and the current element as arguments, and stores the results in a new map. + * An initial value of accumulator is provided by [initialValueSelector] function. + * + * @param initialValueSelector a function that provides an initial value of accumulator for each group. + * It's invoked with parameters: + * - `key`: the key of the group; + * - `element`: the first element being encountered in that group. + * + * @param operation a function that is invoked on each element with the following parameters: + * - `key`: the key of the group this element belongs to; + * - `accumulator`: the current value of the accumulator of the group; + * - `element`: the element from the source being accumulated. + * + * @return a [Map] associating the key of each group with the result of accumulating the group elements. + */ +@SinceKotlin("1.1") +public inline fun Grouping.fold( + initialValueSelector: (key: K, element: T) -> R, + operation: (key: K, accumulator: R, element: T) -> R +): Map = + aggregate { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) } + +/** + * Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially, + * passing the previously accumulated value and the current element as arguments, + * and stores the results in the given [destination] map. + * An initial value of accumulator is provided by [initialValueSelector] function. + * + * @param initialValueSelector a function that provides an initial value of accumulator for each group. + * It's invoked with parameters: + * - `key`: the key of the group; + * - `element`: the first element being encountered in that group. + * + * If the [destination] map already has a value corresponding to some key, that value is used as an initial value of + * the accumulator for that group and the [initialValueSelector] function is not called for that group. + * + * @param operation a function that is invoked on each element with the following parameters: + * - `key`: the key of the group this element belongs to; + * - `accumulator`: the current value of the accumulator of the group; + * - `element`: the element from the source being accumulated. + * + * @return the [destination] map associating the key of each group with the result of accumulating the group elements. + */ +@SinceKotlin("1.1") +public inline fun > Grouping.foldTo( + destination: M, + initialValueSelector: (key: K, element: T) -> R, + operation: (key: K, accumulator: R, element: T) -> R +): M = + aggregateTo(destination) { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) } + + +/** + * Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially, + * passing the previously accumulated value and the current element as arguments, and stores the results in a new map. + * An initial value of accumulator is the same [initialValue] for each group. + * + * @param operation a function that is invoked on each element with the following parameters: + * - `accumulator`: the current value of the accumulator of the group; + * - `element`: the element from the source being accumulated. + * + * @return a [Map] associating the key of each group with the result of accumulating the group elements. + */ +@SinceKotlin("1.1") +public inline fun Grouping.fold( + initialValue: R, + operation: (accumulator: R, element: T) -> R +): Map = + aggregate { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } + +/** + * Groups elements from the [Grouping] source by key and applies [operation] to the elements of each group sequentially, + * passing the previously accumulated value and the current element as arguments, + * and stores the results in the given [destination] map. + * An initial value of accumulator is the same [initialValue] for each group. + * + * If the [destination] map already has a value corresponding to the key of some group, + * that value is used as an initial value of the accumulator for that group. + * + * @param operation a function that is invoked on each element with the following parameters: + * - `accumulator`: the current value of the accumulator of the group; + * - `element`: the element from the source being accumulated. + * + * @return the [destination] map associating the key of each group with the result of accumulating the group elements. + */ +@SinceKotlin("1.1") +public inline fun > Grouping.foldTo( + destination: M, + initialValue: R, + operation: (accumulator: R, element: T) -> R +): M = + aggregateTo(destination) { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } + + +/** + * Groups elements from the [Grouping] source by key and applies the reducing [operation] to the elements of each group + * sequentially starting from the second element of the group, + * passing the previously accumulated value and the current element as arguments, + * and stores the results in a new map. + * An initial value of accumulator is the first element of the group. + * + * @param operation a function that is invoked on each subsequent element of the group with the following parameters: + * - `key`: the key of the group this element belongs to; + * - `accumulator`: the current value of the accumulator of the group; + * - `element`: the element from the source being accumulated. + * + * @return a [Map] associating the key of each group with the result of accumulating the group elements. + */ +@SinceKotlin("1.1") +public inline fun Grouping.reduce( + operation: (key: K, accumulator: S, element: T) -> S +): Map = + aggregate { key, acc, e, first -> + if (first) e else operation(key, acc as S, e) + } + +/** + * Groups elements from the [Grouping] source by key and applies the reducing [operation] to the elements of each group + * sequentially starting from the second element of the group, + * passing the previously accumulated value and the current element as arguments, + * and stores the results in the given [destination] map. + * An initial value of accumulator is the first element of the group. + * + * If the [destination] map already has a value corresponding to the key of some group, + * that value is used as an initial value of the accumulator for that group and the first element of that group is also + * subjected to the [operation]. + + * @param operation a function that is invoked on each subsequent element of the group with the following parameters: + * - `accumulator`: the current value of the accumulator of the group; + * - `element`: the element from the source being folded; + * + * @return the [destination] map associating the key of each group with the result of accumulating the group elements. + */ +@SinceKotlin("1.1") +public inline fun > Grouping.reduceTo( + destination: M, + operation: (key: K, accumulator: S, element: T) -> S +): M = + aggregateTo(destination) { key, acc, e, first -> + if (first) e else operation(key, acc as S, e) + } + + +/** + * Groups elements from the [Grouping] source by key and counts elements in each group to the given [destination] map. + * + * If the [destination] map already has a value corresponding to the key of some group, + * that value is used as an initial value of the counter for that group. + * + * @return the [destination] map associating the key of each group with the count of elements in the group. + * + * @sample samples.collections.Collections.Transformations.groupingByEachCount + */ +@SinceKotlin("1.1") +public fun > Grouping.eachCountTo(destination: M): M = + foldTo(destination, 0) { acc, _ -> acc + 1 } + diff --git a/runtime/src/main/kotlin/kotlin/collections/Maps.kt b/runtime/src/main/kotlin/kotlin/collections/Maps.kt index 86f9f3ad9c2..997c502162d 100644 --- a/runtime/src/main/kotlin/kotlin/collections/Maps.kt +++ b/runtime/src/main/kotlin/kotlin/collections/Maps.kt @@ -116,6 +116,14 @@ public inline fun Map?.orEmpty() : Map = this ?: emptyMap() @kotlin.internal.InlineOnly public inline operator fun <@kotlin.internal.OnlyInputTypes K, V> Map.contains(key: K) : Boolean = containsKey(key) +/** + * Allows to use the index operator for storing values in a mutable map. + */ +@kotlin.internal.InlineOnly +public inline operator fun MutableMap.set(key: K, value: V): Unit { + put(key, value) +} + /** * Returns the value corresponding to the given [key], or `null` if such a key is not present in the map. */ diff --git a/runtime/src/main/kotlin/kotlin/sequences/Sequence.kt b/runtime/src/main/kotlin/kotlin/sequences/Sequence.kt index 3cdeee1c228..5af3b1d32b2 100644 --- a/runtime/src/main/kotlin/kotlin/sequences/Sequence.kt +++ b/runtime/src/main/kotlin/kotlin/sequences/Sequence.kt @@ -20,23 +20,4 @@ public interface Sequence { * Throws an exception if the sequence is constrained to be iterated once and `iterator` is invoked the second time. */ public operator fun iterator(): Iterator -} - -/** - * A sequence that supports drop(n) and take(n) operations - */ -internal interface DropTakeSequence : Sequence { - fun drop(n: Int): Sequence - fun take(n: Int): Sequence -} - -/** - * Returns an empty sequence. - */ -public fun emptySequence(): Sequence = EmptySequence - -private object EmptySequence : Sequence, DropTakeSequence { - override fun iterator(): Iterator = EmptyIterator - override fun drop(n: Int) = EmptySequence - override fun take(n: Int) = EmptySequence } \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/sequences/Sequences.kt b/runtime/src/main/kotlin/kotlin/sequences/Sequences.kt index fe304bbf4e2..42b2f624a6b 100644 --- a/runtime/src/main/kotlin/kotlin/sequences/Sequences.kt +++ b/runtime/src/main/kotlin/kotlin/sequences/Sequences.kt @@ -2,6 +2,607 @@ package kotlin.sequences import kotlin.comparisons.* + +/** + * Given an [iterator] function constructs a [Sequence] that returns values through the [Iterator] + * provided by that function. + * The values are evaluated lazily, and the sequence is potentially infinite. + * + * @sample samples.collections.Sequences.Building.sequenceFromIterator + */ +@kotlin.internal.InlineOnly +public inline fun Sequence(crossinline iterator: () -> Iterator): Sequence = object : Sequence { + override fun iterator(): Iterator = iterator() +} + +/** + * Creates a sequence that returns all values from this enumeration. The sequence is constrained to be iterated only once. + */ +@FixmeSequences +@kotlin.internal.InlineOnly +public inline fun> T.asSequence(): Sequence = TODO() // this.values().iterator().asSequence() + +/** + * Creates a sequence that returns all elements from this iterator. The sequence is constrained to be iterated only once. + * + * @sample samples.collections.Sequences.Building.sequenceFromIterator + */ +public fun Iterator.asSequence(): Sequence = Sequence { this }.constrainOnce() + +/** + * Creates a sequence that returns the specified values. + * + * @sample samples.collections.Sequences.Building.sequenceOfValues + */ +public fun sequenceOf(vararg elements: T): Sequence = if (elements.isEmpty()) emptySequence() else elements.asSequence() + +/** + * Returns an empty sequence. + */ +public fun emptySequence(): Sequence = EmptySequence + +private object EmptySequence : Sequence, DropTakeSequence { + override fun iterator(): Iterator = EmptyIterator + override fun drop(n: Int) = EmptySequence + override fun take(n: Int) = EmptySequence +} + +/** + * Returns a sequence of all elements from all sequences in this sequence. + */ +public fun Sequence>.flatten(): Sequence = flatten { it.iterator() } + +/** + * Returns a sequence of all elements from all iterables in this sequence. + */ +public fun Sequence>.flatten(): Sequence = flatten { it.iterator() } + +private fun Sequence.flatten(iterator: (T) -> Iterator): Sequence { + if (this is TransformingSequence<*, *>) { + return (this as TransformingSequence<*, T>).flatten(iterator) + } + return FlatteningSequence(this, { it }, iterator) +} + +/** + * Returns a pair of lists, where + * *first* list is built from the first values of each pair from this sequence, + * *second* list is built from the second values of each pair from this sequence. + */ +public fun Sequence>.unzip(): Pair, List> { + val listT = ArrayList() + val listR = ArrayList() + for (pair in this) { + listT.add(pair.first) + listR.add(pair.second) + } + return listT to listR +} + +/** + * A sequence that returns the values from the underlying [sequence] that either match or do not match + * the specified [predicate]. + * + * @param sendWhen If `true`, values for which the predicate returns `true` are returned. Otherwise, + * values for which the predicate returns `false` are returned + */ +internal class FilteringSequence(private val sequence: Sequence, + private val sendWhen: Boolean = true, + private val predicate: (T) -> Boolean +) : Sequence { + + override fun iterator(): Iterator = object : Iterator { + val iterator = sequence.iterator() + var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue + var nextItem: T? = null + + private fun calcNext() { + while (iterator.hasNext()) { + val item = iterator.next() + if (predicate(item) == sendWhen) { + nextItem = item + nextState = 1 + return + } + } + nextState = 0 + } + + override fun next(): T { + if (nextState == -1) + calcNext() + if (nextState == 0) + throw NoSuchElementException() + val result = nextItem + nextItem = null + nextState = -1 + return result as T + } + + override fun hasNext(): Boolean { + if (nextState == -1) + calcNext() + return nextState == 1 + } + } +} + +/** + * A sequence which returns the results of applying the given [transformer] function to the values + * in the underlying [sequence]. + */ + +internal class TransformingSequence +constructor(private val sequence: Sequence, private val transformer: (T) -> R) : Sequence { + override fun iterator(): Iterator = object : Iterator { + val iterator = sequence.iterator() + override fun next(): R { + return transformer(iterator.next()) + } + + override fun hasNext(): Boolean { + return iterator.hasNext() + } + } + + internal fun flatten(iterator: (R) -> Iterator): Sequence { + return FlatteningSequence(sequence, transformer, iterator) + } +} + +/** + * A sequence which returns the results of applying the given [transformer] function to the values + * in the underlying [sequence], where the transformer function takes the index of the value in the underlying + * sequence along with the value itself. + */ +internal class TransformingIndexedSequence +constructor(private val sequence: Sequence, private val transformer: (Int, T) -> R) : Sequence { + override fun iterator(): Iterator = object : Iterator { + val iterator = sequence.iterator() + var index = 0 + override fun next(): R { + return transformer(index++, iterator.next()) + } + + override fun hasNext(): Boolean { + return iterator.hasNext() + } + } +} + +/** + * A sequence which combines values from the underlying [sequence] with their indices and returns them as + * [IndexedValue] objects. + */ +internal class IndexingSequence +constructor(private val sequence: Sequence) : Sequence> { + override fun iterator(): Iterator> = object : Iterator> { + val iterator = sequence.iterator() + var index = 0 + override fun next(): IndexedValue { + return IndexedValue(index++, iterator.next()) + } + + override fun hasNext(): Boolean { + return iterator.hasNext() + } + } +} + +/** + * A sequence which takes the values from two parallel underlying sequences, passes them to the given + * [transform] function and returns the values returned by that function. The sequence stops returning + * values as soon as one of the underlying sequences stops returning values. + */ +internal class MergingSequence +constructor(private val sequence1: Sequence, + private val sequence2: Sequence, + private val transform: (T1, T2) -> V +) : Sequence { + override fun iterator(): Iterator = object : Iterator { + val iterator1 = sequence1.iterator() + val iterator2 = sequence2.iterator() + override fun next(): V { + return transform(iterator1.next(), iterator2.next()) + } + + override fun hasNext(): Boolean { + return iterator1.hasNext() && iterator2.hasNext() + } + } +} + +internal class FlatteningSequence +constructor( + private val sequence: Sequence, + private val transformer: (T) -> R, + private val iterator: (R) -> Iterator +) : Sequence { + override fun iterator(): Iterator = object : Iterator { + val iterator = sequence.iterator() + var itemIterator: Iterator? = null + + override fun next(): E { + if (!ensureItemIterator()) + throw NoSuchElementException() + return itemIterator!!.next() + } + + override fun hasNext(): Boolean { + return ensureItemIterator() + } + + private fun ensureItemIterator(): Boolean { + if (itemIterator?.hasNext() == false) + itemIterator = null + + while (itemIterator == null) { + if (!iterator.hasNext()) { + return false + } else { + val element = iterator.next() + val nextItemIterator = iterator(transformer(element)) + if (nextItemIterator.hasNext()) { + itemIterator = nextItemIterator + return true + } + } + } + return true + } + } +} + +/** + * A sequence that supports drop(n) and take(n) operations + */ +internal interface DropTakeSequence : Sequence { + fun drop(n: Int): Sequence + fun take(n: Int): Sequence +} + +/** + * A sequence that skips [startIndex] values from the underlying [sequence] + * and stops returning values right before [endIndex], i.e. stops at `endIndex - 1` + */ +internal class SubSequence ( + private val sequence: Sequence, + private val startIndex: Int, + private val endIndex: Int +): Sequence, DropTakeSequence { + + init { + require(startIndex >= 0) { "startIndex should be non-negative, but is $startIndex" } + require(endIndex >= 0) { "endIndex should be non-negative, but is $endIndex" } + require(endIndex >= startIndex) { "endIndex should be not less than startIndex, but was $endIndex < $startIndex"} + } + + private val count: Int get() = endIndex - startIndex + override fun drop(n: Int): Sequence = if (n >= count) emptySequence() else SubSequence(sequence, startIndex + n, endIndex) + override fun take(n: Int): Sequence = if (n >= count) this else SubSequence(sequence, startIndex, startIndex + n) + + override fun iterator() = object : Iterator { + + val iterator = sequence.iterator() + var position = 0 + + // Shouldn't be called from constructor to avoid premature iteration + private fun drop() { + while(position < startIndex && iterator.hasNext()) { + iterator.next() + position++ + } + } + + override fun hasNext(): Boolean { + drop() + return (position < endIndex) && iterator.hasNext() + } + + override fun next(): T { + drop() + if (position >= endIndex) + throw NoSuchElementException() + position++ + return iterator.next() + } + } +} + +/** + * A sequence that returns at most [count] values from the underlying [sequence], and stops returning values + * as soon as that count is reached. + */ +internal class TakeSequence ( + private val sequence: Sequence, + private val count: Int +) : Sequence, DropTakeSequence { + + init { + require (count >= 0) { "count must be non-negative, but was $count." } + } + + override fun drop(n: Int): Sequence = if (n >= count) emptySequence() else SubSequence(sequence, n, count) + override fun take(n: Int): Sequence = if (n >= count) this else TakeSequence(sequence, n) + + override fun iterator(): Iterator = object : Iterator { + var left = count + val iterator = sequence.iterator() + + override fun next(): T { + if (left == 0) + throw NoSuchElementException() + left-- + return iterator.next() + } + + override fun hasNext(): Boolean { + return left > 0 && iterator.hasNext() + } + } +} + +/** + * A sequence that returns values from the underlying [sequence] while the [predicate] function returns + * `true`, and stops returning values once the function returns `false` for the next element. + */ +internal class TakeWhileSequence +constructor(private val sequence: Sequence, + private val predicate: (T) -> Boolean +) : Sequence { + override fun iterator(): Iterator = object : Iterator { + val iterator = sequence.iterator() + var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue + var nextItem: T? = null + + private fun calcNext() { + if (iterator.hasNext()) { + val item = iterator.next() + if (predicate(item)) { + nextState = 1 + nextItem = item + return + } + } + nextState = 0 + } + + override fun next(): T { + if (nextState == -1) + calcNext() // will change nextState + if (nextState == 0) + throw NoSuchElementException() + val result = nextItem as T + + // Clean next to avoid keeping reference on yielded instance + nextItem = null + nextState = -1 + return result + } + + override fun hasNext(): Boolean { + if (nextState == -1) + calcNext() // will change nextState + return nextState == 1 + } + } +} + +/** + * A sequence that skips the specified number of values from the underlying [sequence] and returns + * all values after that. + */ +internal class DropSequence ( + private val sequence: Sequence, + private val count: Int +) : Sequence, DropTakeSequence { + init { + require (count >= 0) { "count must be non-negative, but was $count." } + } + + override fun drop(n: Int): Sequence = DropSequence(sequence, count + n) + override fun take(n: Int): Sequence = SubSequence(sequence, count, count + n) + + override fun iterator(): Iterator = object : Iterator { + val iterator = sequence.iterator() + var left = count + + // Shouldn't be called from constructor to avoid premature iteration + private fun drop() { + while (left > 0 && iterator.hasNext()) { + iterator.next() + left-- + } + } + + override fun next(): T { + drop() + return iterator.next() + } + + override fun hasNext(): Boolean { + drop() + return iterator.hasNext() + } + } +} + +/** + * A sequence that skips the values from the underlying [sequence] while the given [predicate] returns `true` and returns + * all values after that. + */ +internal class DropWhileSequence +constructor(private val sequence: Sequence, + private val predicate: (T) -> Boolean +) : Sequence { + + override fun iterator(): Iterator = object : Iterator { + val iterator = sequence.iterator() + var dropState: Int = -1 // -1 for not dropping, 1 for nextItem, 0 for normal iteration + var nextItem: T? = null + + private fun drop() { + while (iterator.hasNext()) { + val item = iterator.next() + if (!predicate(item)) { + nextItem = item + dropState = 1 + return + } + } + dropState = 0 + } + + override fun next(): T { + if (dropState == -1) + drop() + + if (dropState == 1) { + val result = nextItem as T + nextItem = null + dropState = 0 + return result + } + return iterator.next() + } + + override fun hasNext(): Boolean { + if (dropState == -1) + drop() + return dropState == 1 || iterator.hasNext() + } + } +} + + +internal class DistinctSequence(private val source : Sequence, private val keySelector : (T) -> K) : Sequence { + override fun iterator(): Iterator = DistinctIterator(source.iterator(), keySelector) +} + +private class DistinctIterator(private val source : Iterator, private val keySelector : (T) -> K) : AbstractIterator() { + private val observed = HashSet() + + override fun computeNext() { + while (source.hasNext()) { + val next = source.next() + val key = keySelector(next) + + if (observed.add(key)) { + setNext(next) + return + } + } + + done() + } +} + +private class GeneratorSequence(private val getInitialValue: () -> T?, private val getNextValue: (T) -> T?): Sequence { + override fun iterator(): Iterator = object : Iterator { + var nextItem: T? = null + var nextState: Int = -2 // -2 for initial unknown, -1 for next unknown, 0 for done, 1 for continue + + private fun calcNext() { + nextItem = if (nextState == -2) getInitialValue() else getNextValue(nextItem!!) + nextState = if (nextItem == null) 0 else 1 + } + + override fun next(): T { + if (nextState < 0) + calcNext() + + if (nextState == 0) + throw NoSuchElementException() + val result = nextItem as T + // Do not clean nextItem (to avoid keeping reference on yielded instance) -- need to keep state for getNextValue + nextState = -1 + return result + } + + override fun hasNext(): Boolean { + if (nextState < 0) + calcNext() + return nextState == 1 + } + } +} + +/** + * 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 Sequence.constrainOnce(): Sequence { + // as? does not work in js + //return this as? ConstrainedOnceSequence ?: ConstrainedOnceSequence(this) + return if (this is ConstrainedOnceSequence) this else ConstrainedOnceSequence(this) +} + +@FixmeConcurrency +private class ConstrainedOnceSequence(sequence: Sequence) : Sequence { + // TODO: not MT friendly. + private var sequenceRef : Sequence? = sequence + + override fun iterator(): Iterator { + val sequence = sequenceRef + if (sequence == null) throw IllegalStateException("This sequence can be consumed only once.") + sequenceRef = null + return sequence.iterator() + } +} + +/** + * Returns a sequence which invokes the function to calculate the next value on each iteration until the function returns `null`. + * + * The returned sequence is constrained to be iterated only once. + * + * @see constrainOnce + * @see kotlin.coroutines.experimental.buildSequence + * + * @sample samples.collections.Sequences.Building.generateSequence + */ +public fun generateSequence(nextFunction: () -> T?): Sequence { + return GeneratorSequence(nextFunction, { nextFunction() }).constrainOnce() +} + +/** + * Returns a sequence defined by the starting value [seed] and the function [nextFunction], + * which is invoked to calculate the next value based on the previous one on each iteration. + * + * The sequence produces values until it encounters first `null` value. + * If [seed] is `null`, an empty sequence is produced. + * + * The sequence can be iterated multiple times, each time starting with [seed]. + * + * @see kotlin.coroutines.experimental.buildSequence + * + * @sample samples.collections.Sequences.Building.generateSequenceWithSeed + */ +@kotlin.internal.LowPriorityInOverloadResolution +public fun generateSequence(seed: T?, nextFunction: (T) -> T?): Sequence = + if (seed == null) + EmptySequence + else + GeneratorSequence({ seed }, nextFunction) + +/** + * Returns a sequence defined by the function [seedFunction], which is invoked to produce the starting value, + * and the [nextFunction], which is invoked to calculate the next value based on the previous one on each iteration. + * + * The sequence produces values until it encounters first `null` value. + * If [seedFunction] returns `null`, an empty sequence is produced. + * + * The sequence can be iterated multiple times. + * + * @see kotlin.coroutines.experimental.buildSequence + * + * @sample samples.collections.Sequences.Building.generateSequenceWithLazySeed + */ +public fun generateSequence(seedFunction: () -> T?, nextFunction: (T) -> T?): Sequence = + GeneratorSequence(seedFunction, nextFunction) + + + /** * Returns `true` if [element] is found in the sequence. */ @@ -276,72 +877,20 @@ public inline fun Sequence.singleOrNull(predicate: (T) -> Boolean): T? { /** * Returns a sequence containing all elements except first [n] elements. */ -@FixmeSequences public fun Sequence.drop(n: Int): Sequence { require(n >= 0) { "Requested element count $n is less than zero." } - TODO() - //return when { - // n == 0 -> this - // this is DropTakeSequence -> this.drop(n) - // else -> DropSequence(this, n) - //} + return when { + n == 0 -> this + this is DropTakeSequence -> this.drop(n) + else -> DropSequence(this, n) + } } /** * Returns a sequence containing all elements except first elements that satisfy the given [predicate]. */ -@FixmeSequences public fun Sequence.dropWhile(predicate: (T) -> Boolean): Sequence { - TODO() - //return DropWhileSequence(this, predicate) -} - -/** - * A sequence that returns the values from the underlying [sequence] that either match or do not match - * the specified [predicate]. - * - * @param sendWhen If `true`, values for which the predicate returns `true` are returned. Otherwise, - * values for which the predicate returns `false` are returned - */ -internal class FilteringSequence(private val sequence: Sequence, - private val sendWhen: Boolean = true, - private val predicate: (T) -> Boolean -) : Sequence { - - override fun iterator(): Iterator = object : Iterator { - val iterator = sequence.iterator() - var nextState: Int = -1 // -1 for unknown, 0 for done, 1 for continue - var nextItem: T? = null - - private fun calcNext() { - while (iterator.hasNext()) { - val item = iterator.next() - if (predicate(item) == sendWhen) { - nextItem = item - nextState = 1 - return - } - } - nextState = 0 - } - - override fun next(): T { - if (nextState == -1) - calcNext() - if (nextState == 0) - throw NoSuchElementException() - val result = nextItem - nextItem = null - nextState = -1 - return result as T - } - - override fun hasNext(): Boolean { - if (nextState == -1) - calcNext() - return nextState == 1 - } - } + return DropWhileSequence(this, predicate) } /** @@ -356,11 +905,9 @@ public fun Sequence.filter(predicate: (T) -> Boolean): Sequence { * @param [predicate] function that takes the index of an element and the element itself * and returns the result of predicate evaluation on the element. */ -@Fixme public fun Sequence.filterIndexed(predicate: (Int, T) -> Boolean): Sequence { - TODO() // TODO: Rewrite with generalized MapFilterIndexingSequence - // return TransformingSequence(FilteringSequence(IndexingSequence(this), true, { predicate(it.index, it.value) }), { it.value }) + return TransformingSequence(FilteringSequence(IndexingSequence(this), true, { predicate(it.index, it.value) }), { it.value }) } /** @@ -437,24 +984,20 @@ public inline fun > Sequence.filterTo(destinat /** * Returns a sequence containing first [n] elements. */ -@FixmeSequences public fun Sequence.take(n: Int): Sequence { - //require(n >= 0) { "Requested element count $n is less than zero." } - //return when { - // n == 0 -> emptySequence() - // this is DropTakeSequence -> this.take(n) - // else -> TakeSequence(this, n) - //} - TODO() + require(n >= 0) { "Requested element count $n is less than zero." } + return when { + n == 0 -> emptySequence() + this is DropTakeSequence -> this.take(n) + else -> TakeSequence(this, n) + } } /** * Returns a sequence containing first elements satisfying the given [predicate]. */ -@FixmeSequences public fun Sequence.takeWhile(predicate: (T) -> Boolean): Sequence { - // return TakeWhileSequence(this, predicate) - TODO() + return TakeWhileSequence(this, predicate) } /** @@ -623,10 +1166,8 @@ public fun Sequence.toSet(): Set { /** * Returns a single sequence of all elements from results of [transform] function being invoked on each element of original sequence. */ -@FixmeSequences public fun Sequence.flatMap(transform: (T) -> Sequence): Sequence { - // return FlatteningSequence(this, transform, { it.iterator() }) - TODO() + return FlatteningSequence(this, transform, { it.iterator() }) } /** @@ -704,23 +1245,19 @@ public inline fun >> Sequence.gr * Creates a [Grouping] source from a sequence to be used later with one of group-and-fold operations * using the specified [keySelector] function to extract a key from each element. */ -// @Fixme -// public inline fun Sequence.groupingBy(crossinline keySelector: (T) -> K): Grouping { -// TODO() - //return object : Grouping { - // override fun sourceIterator(): Iterator = this@groupingBy.iterator() - // override fun keyOf(element: T): K = keySelector(element) - //} -//} +public inline fun Sequence.groupingBy(crossinline keySelector: (T) -> K): Grouping { + return object : Grouping { + override fun sourceIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: T): K = keySelector(element) + } +} /** * Returns a sequence containing the results of applying the given [transform] function * to each element in the original sequence. */ -@FixmeSequences public fun Sequence.map(transform: (T) -> R): Sequence { - // return TransformingSequence(this, transform) - TODO() + return TransformingSequence(this, transform) } /** @@ -729,10 +1266,8 @@ public fun Sequence.map(transform: (T) -> R): Sequence { * @param [transform] function that takes the index of an element and the element itself * and returns the result of the transform applied to the element. */ -@FixmeSequences public fun Sequence.mapIndexed(transform: (Int, T) -> R): Sequence { - TODO() - //return TransformingIndexedSequence(this, transform) + return TransformingIndexedSequence(this, transform) } /** @@ -741,10 +1276,8 @@ public fun Sequence.mapIndexed(transform: (Int, T) -> R): Sequence * @param [transform] function that takes the index of an element and the element itself * and returns the result of the transform applied to the element. */ -@FixmeSequences public fun Sequence.mapIndexedNotNull(transform: (Int, T) -> R?): Sequence { - TODO() - //return TransformingIndexedSequence(this, transform).filterNotNull() + return TransformingIndexedSequence(this, transform).filterNotNull() } /** @@ -775,10 +1308,8 @@ public inline fun > Sequence.mapIndexedTo(d * Returns a sequence containing only the non-null results of applying the given [transform] function * to each element in the original sequence. */ -@FixmeSequences public fun Sequence.mapNotNull(transform: (T) -> R?): Sequence { - // return TransformingSequence(this, transform).filterNotNull() - TODO() + return TransformingSequence(this, transform).filterNotNull() } /** @@ -803,10 +1334,8 @@ public inline fun > Sequence.mapTo(destinat /** * Returns a sequence of [IndexedValue] for each element of the original sequence. */ -@Fixme public fun Sequence.withIndex(): Sequence> { - // return IndexingSequence(this) - TODO() + return IndexingSequence(this) } /** @@ -824,10 +1353,8 @@ public fun Sequence.distinct(): Sequence { * * The elements in the resulting sequence are in the same order as they were in the source sequence. */ -@FixmeSequences public fun Sequence.distinctBy(selector: (T) -> K): Sequence { - // return DistinctSequence(this, selector) - TODO() + return DistinctSequence(this, selector) } /** @@ -1266,10 +1793,8 @@ public inline fun Sequence.partition(predicate: (T) -> Boolean): Pair Sequence.plus(element: T): Sequence { - TODO() - // return sequenceOf(this, sequenceOf(element)).flatten() + return sequenceOf(this, sequenceOf(element)).flatten() } /** @@ -1288,10 +1813,8 @@ public operator fun Sequence.plus(elements: Array): Sequence { * Note that the source sequence and the collection being added are iterated only when an `iterator` is requested from * the resulting sequence. Changing any of them between successive calls to `iterator` may affect the result. */ -@FixmeSequences public operator fun Sequence.plus(elements: Iterable): Sequence { - // return sequenceOf(this, elements.asSequence()).flatten() - TODO() + return sequenceOf(this, elements.asSequence()).flatten() } /** @@ -1300,10 +1823,8 @@ public operator fun Sequence.plus(elements: Iterable): Sequence { * Note that the source sequence and the sequence being added are iterated only when an `iterator` is requested from * the resulting sequence. Changing any of them between successive calls to `iterator` may affect the result. */ -@FixmeSequences public operator fun Sequence.plus(elements: Sequence): Sequence { - //return sequenceOf(this, elements).flatten() - TODO() + return sequenceOf(this, elements).flatten() } /** @@ -1318,19 +1839,15 @@ public inline fun Sequence.plusElement(element: T): Sequence { * Returns a sequence of pairs built from elements of both sequences with same indexes. * Resulting sequence has length of shortest input sequence. */ -@FixmeSequences public infix fun Sequence.zip(other: Sequence): Sequence> { - TODO() - //return MergingSequence(this, other) { t1, t2 -> t1 to t2 } + return MergingSequence(this, other) { t1, t2 -> t1 to t2 } } /** * Returns a sequence of values built from elements of both collections with same indexes using provided [transform]. Resulting sequence has length of shortest input sequences. */ -@FixmeSequences public fun Sequence.zip(other: Sequence, transform: (T, R) -> V): Sequence { - // return MergingSequence(this, other, transform) - TODO() + return MergingSequence(this, other, transform) } /** @@ -1339,10 +1856,7 @@ public fun Sequence.zip(other: Sequence, transform: (T, R) -> V) * If the collection could be huge, you can specify a non-negative value of [limit], in which case only the first [limit] * elements will be appended, followed by the [truncated] string (which defaults to "..."). */ -@FixmeSequences public fun Sequence.joinTo(buffer: A, separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): A { - TODO() - /* buffer.append(prefix) var count = 0 for (element in this) { @@ -1353,7 +1867,7 @@ public fun Sequence.joinTo(buffer: A, separator: CharSequ } if (limit >= 0 && count > limit) buffer.append(truncated) buffer.append(postfix) - return buffer */ + return buffer } /**