From e6da367fe08464010306d04ae6b34dfef0c1210f Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Wed, 28 Dec 2016 17:49:35 +0300 Subject: [PATCH 1/5] KEEP-23 implement group and fold operations Docs for group-and-fold operations Fix implementation for countEach and sumEachBy countEach and sumEachBy implementations for JS Rename keySelector to keyOf Generate additional sources for groupingBy + headers Rename countEach and sumEachBy to eachCount and eachSumOf. --- .../src/core/generated/_ArraysJs.kt | 12 + .../src/core/generated/_CollectionsJs.kt | 12 + .../src/core/generated/_SequencesJs.kt | 12 + .../src/core/generated/_StringsJs.kt | 12 + js/js.libraries/src/core/grouping.kt | 34 +++ .../stdlib/common/src/generated/_Arrays.kt | 7 + .../common/src/generated/_Collections.kt | 7 + .../stdlib/common/src/generated/_Sequences.kt | 7 + .../stdlib/common/src/generated/_Strings.kt | 7 + .../stdlib/common/src/kotlin/CollectionsH.kt | 5 + libraries/stdlib/src/generated/_Arrays.kt | 12 + .../stdlib/src/generated/_Collections.kt | 12 + libraries/stdlib/src/generated/_Sequences.kt | 12 + libraries/stdlib/src/generated/_Strings.kt | 12 + .../stdlib/src/kotlin/collections/Grouping.kt | 278 ++++++++++++++++++ .../reference-public-api/kotlin-stdlib.txt | 24 ++ .../src/templates/Mapping.kt | 27 ++ 17 files changed, 492 insertions(+) create mode 100644 js/js.libraries/src/core/grouping.kt create mode 100644 libraries/stdlib/src/kotlin/collections/Grouping.kt diff --git a/js/js.libraries/src/core/generated/_ArraysJs.kt b/js/js.libraries/src/core/generated/_ArraysJs.kt index 4b422050ddc62..1e8a66aeea7e0 100644 --- a/js/js.libraries/src/core/generated/_ArraysJs.kt +++ b/js/js.libraries/src/core/generated/_ArraysJs.kt @@ -7630,6 +7630,18 @@ public inline fun >> CharArray.groupBy return destination } +/** + * Creates a [Grouping] source from an array to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each element. + */ +@SinceKotlin("1.1") +public inline fun Array.groupingBy(crossinline keySelector: (T) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: T): K = keySelector(element) + } +} + /** * Returns a list containing the results of applying the given [transform] function * to each element in the original array. diff --git a/js/js.libraries/src/core/generated/_CollectionsJs.kt b/js/js.libraries/src/core/generated/_CollectionsJs.kt index e3aebdd3dcdb6..65769d2c2ee78 100644 --- a/js/js.libraries/src/core/generated/_CollectionsJs.kt +++ b/js/js.libraries/src/core/generated/_CollectionsJs.kt @@ -1179,6 +1179,18 @@ public inline fun >> Iterable.gr return destination } +/** + * Creates a [Grouping] source from a collection to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each element. + */ +@SinceKotlin("1.1") +public inline fun Iterable.groupingBy(crossinline keySelector: (T) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: T): K = keySelector(element) + } +} + /** * Returns a list containing the results of applying the given [transform] function * to each element in the original collection. diff --git a/js/js.libraries/src/core/generated/_SequencesJs.kt b/js/js.libraries/src/core/generated/_SequencesJs.kt index 1774a61b6868f..2cdb0fdeb3803 100644 --- a/js/js.libraries/src/core/generated/_SequencesJs.kt +++ b/js/js.libraries/src/core/generated/_SequencesJs.kt @@ -644,6 +644,18 @@ public inline fun >> Sequence.gr return destination } +/** + * 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. + */ +@SinceKotlin("1.1") +public inline fun Sequence.groupingBy(crossinline keySelector: (T) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): 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. diff --git a/js/js.libraries/src/core/generated/_StringsJs.kt b/js/js.libraries/src/core/generated/_StringsJs.kt index ad95686cc2979..34ca23c00656e 100644 --- a/js/js.libraries/src/core/generated/_StringsJs.kt +++ b/js/js.libraries/src/core/generated/_StringsJs.kt @@ -701,6 +701,18 @@ public inline fun >> CharSequence.grou return destination } +/** + * Creates a [Grouping] source from a char sequence to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each character. + */ +@SinceKotlin("1.1") +public inline fun CharSequence.groupingBy(crossinline keySelector: (Char) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: Char): K = keySelector(element) + } +} + /** * Returns a list containing the results of applying the given [transform] function * to each character in the original char sequence. diff --git a/js/js.libraries/src/core/grouping.kt b/js/js.libraries/src/core/grouping.kt new file mode 100644 index 0000000000000..4c878b857b9d9 --- /dev/null +++ b/js/js.libraries/src/core/grouping.kt @@ -0,0 +1,34 @@ +/* + * 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 + +/** + * Groups elements from the [Grouping] source by key and counts elements in each group. + * + * @return a [Map] associating the key of each group with the count of element in the group. + */ +@SinceKotlin("1.1") +public fun Grouping.eachCount(): Map = + fold(0) { acc, e -> acc + 1 } + +/** + * Groups elements from the [Grouping] source by key and sums values provided by the [valueSelector] function for elements in each group. + * + * @return a [Map] associating the key of each group with the count of element in the group. + */ +@SinceKotlin("1.1") +public inline fun Grouping.eachSumOf(valueSelector: (T) -> Int): Map = + fold(0) { acc, e -> acc + valueSelector(e) } \ No newline at end of file diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index 62778d3e8e06d..702725748dd3d 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -4445,6 +4445,13 @@ public header inline fun >> BooleanArr */ public header inline fun >> CharArray.groupByTo(destination: M, keySelector: (Char) -> K, valueTransform: (Char) -> V): M +/** + * Creates a [Grouping] source from an array to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each element. + */ +@SinceKotlin("1.1") +public header inline fun Array.groupingBy(crossinline keySelector: (T) -> K): Grouping + /** * Returns a list containing the results of applying the given [transform] function * to each element in the original array. diff --git a/libraries/stdlib/common/src/generated/_Collections.kt b/libraries/stdlib/common/src/generated/_Collections.kt index 8165d4105126c..074d3bbe95326 100644 --- a/libraries/stdlib/common/src/generated/_Collections.kt +++ b/libraries/stdlib/common/src/generated/_Collections.kt @@ -587,6 +587,13 @@ public header inline fun >> Iterable>> Iterable.groupByTo(destination: M, keySelector: (T) -> K, valueTransform: (T) -> V): M +/** + * Creates a [Grouping] source from a collection to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each element. + */ +@SinceKotlin("1.1") +public header inline fun Iterable.groupingBy(crossinline keySelector: (T) -> K): Grouping + /** * Returns a list containing the results of applying the given [transform] function * to each element in the original collection. diff --git a/libraries/stdlib/common/src/generated/_Sequences.kt b/libraries/stdlib/common/src/generated/_Sequences.kt index 2a4b0b2ce2c6e..093151ec4cf5b 100644 --- a/libraries/stdlib/common/src/generated/_Sequences.kt +++ b/libraries/stdlib/common/src/generated/_Sequences.kt @@ -359,6 +359,13 @@ public header inline fun >> Sequence>> Sequence.groupByTo(destination: M, keySelector: (T) -> K, valueTransform: (T) -> V): M +/** + * 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. + */ +@SinceKotlin("1.1") +public header inline fun Sequence.groupingBy(crossinline keySelector: (T) -> K): Grouping + /** * Returns a sequence containing the results of applying the given [transform] function * to each element in the original sequence. diff --git a/libraries/stdlib/common/src/generated/_Strings.kt b/libraries/stdlib/common/src/generated/_Strings.kt index 4d9d91b55a137..197e1916f4bc1 100644 --- a/libraries/stdlib/common/src/generated/_Strings.kt +++ b/libraries/stdlib/common/src/generated/_Strings.kt @@ -422,6 +422,13 @@ public header inline fun >> CharSequen */ public header inline fun >> CharSequence.groupByTo(destination: M, keySelector: (Char) -> K, valueTransform: (Char) -> V): M +/** + * Creates a [Grouping] source from a char sequence to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each character. + */ +@SinceKotlin("1.1") +public header inline fun CharSequence.groupingBy(crossinline keySelector: (Char) -> K): Grouping + /** * Returns a list containing the results of applying the given [transform] function * to each character in the original char sequence. diff --git a/libraries/stdlib/common/src/kotlin/CollectionsH.kt b/libraries/stdlib/common/src/kotlin/CollectionsH.kt index ec705b72c6fb7..15fe184305583 100644 --- a/libraries/stdlib/common/src/kotlin/CollectionsH.kt +++ b/libraries/stdlib/common/src/kotlin/CollectionsH.kt @@ -154,3 +154,8 @@ header fun MutableList.sortWith(comparator: Comparator): Unit // from Maps.kt header operator fun MutableMap.set(key: K, value: V): Unit + + +// from Grouping.kt +public header fun Grouping.eachCount(): Map +public header inline fun Grouping.eachSumOf(valueSelector: (T) -> Int): Map \ No newline at end of file diff --git a/libraries/stdlib/src/generated/_Arrays.kt b/libraries/stdlib/src/generated/_Arrays.kt index c9ba745e2f68e..9f5894eab7910 100644 --- a/libraries/stdlib/src/generated/_Arrays.kt +++ b/libraries/stdlib/src/generated/_Arrays.kt @@ -7713,6 +7713,18 @@ public inline fun >> CharArray.groupBy return destination } +/** + * Creates a [Grouping] source from an array to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each element. + */ +@SinceKotlin("1.1") +public inline fun Array.groupingBy(crossinline keySelector: (T) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: T): K = keySelector(element) + } +} + /** * Returns a list containing the results of applying the given [transform] function * to each element in the original array. diff --git a/libraries/stdlib/src/generated/_Collections.kt b/libraries/stdlib/src/generated/_Collections.kt index a76a514a32263..5cda36f3a1fcf 100644 --- a/libraries/stdlib/src/generated/_Collections.kt +++ b/libraries/stdlib/src/generated/_Collections.kt @@ -1190,6 +1190,18 @@ public inline fun >> Iterable.gr return destination } +/** + * Creates a [Grouping] source from a collection to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each element. + */ +@SinceKotlin("1.1") +public inline fun Iterable.groupingBy(crossinline keySelector: (T) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: T): K = keySelector(element) + } +} + /** * Returns a list containing the results of applying the given [transform] function * to each element in the original collection. diff --git a/libraries/stdlib/src/generated/_Sequences.kt b/libraries/stdlib/src/generated/_Sequences.kt index a1367cafddbac..fbdd73f675adf 100644 --- a/libraries/stdlib/src/generated/_Sequences.kt +++ b/libraries/stdlib/src/generated/_Sequences.kt @@ -663,6 +663,18 @@ public inline fun >> Sequence.gr return destination } +/** + * 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. + */ +@SinceKotlin("1.1") +public inline fun Sequence.groupingBy(crossinline keySelector: (T) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): 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. diff --git a/libraries/stdlib/src/generated/_Strings.kt b/libraries/stdlib/src/generated/_Strings.kt index 9b3c17dd5ba5c..c02cdf081a0e0 100644 --- a/libraries/stdlib/src/generated/_Strings.kt +++ b/libraries/stdlib/src/generated/_Strings.kt @@ -710,6 +710,18 @@ public inline fun >> CharSequence.grou return destination } +/** + * Creates a [Grouping] source from a char sequence to be used later with one of group-and-fold operations + * using the specified [keySelector] function to extract a key from each character. + */ +@SinceKotlin("1.1") +public inline fun CharSequence.groupingBy(crossinline keySelector: (Char) -> K): Grouping { + return object : Grouping { + override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: Char): K = keySelector(element) + } +} + /** * Returns a list containing the results of applying the given [transform] function * to each character in the original char sequence. diff --git a/libraries/stdlib/src/kotlin/collections/Grouping.kt b/libraries/stdlib/src/kotlin/collections/Grouping.kt new file mode 100644 index 0000000000000..d72aa4333b52c --- /dev/null +++ b/libraries/stdlib/src/kotlin/collections/Grouping.kt @@ -0,0 +1,278 @@ +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] which iterates through the elements of the source. */ + fun elementIterator(): Iterator + /** Extracts the key of an [element]. */ + fun keyOf(element: T): K +} + +/** + * Groups elements from the [Grouping] source by key and aggregates elements of each group with the specified [operation]. + * + * 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 a group this element belongs to; + * - `value`: the current value of the accumulator of a group, can be `null` if it's first `element` encountered in the group; + * - `element`: the element from the source being aggregated; + * - `first`: indicates whether it's 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, value: R?, element: T, first: Boolean) -> R +): Map { + val result = mutableMapOf() + for (e in this.elementIterator()) { + val key = keyOf(e) + val value = result[key] + result[key] = operation(key, value, e, value == null && !result.containsKey(key)) + } + return result +} + +/** + * Groups elements from the [Grouping] source by key and aggregates elements of each group with the specified [operation] + * to 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 a group this element belongs to; + * - `accumulator`: the current value of the accumulator of the group, can be `null` if it's first `element` encountered in the group; + * - `element`: the element from the source being aggregated; + * - `first`: indicates whether it's 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.elementIterator()) { + val key = keyOf(e) + val acc = destination[key] + destination[key] = operation(key, acc, e, acc == null && !destination.containsKey(key)) + } + return destination +} + +/** + * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] + * starting with an initial value of accumulator provided by the [initialValueSelector] function. + * + * @param initialValueSelector a function that provides an initial value of accumulator for an each group. + * It's invoked with parameters: + * - `key`: the key of a 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 a 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, value, e, first -> operation(key, if (first) initialValueSelector(key, e) else value as R, e) } + +/** + * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] + * starting with an initial value of accumulator provided by the [initialValueSelector] function + * to the given [destination] map. + * + * @param initialValueSelector a function that provides an initial value of accumulator for an each group. + * It's invoked with parameters: + * - `key`: the key of a 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 a 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, value, e, first -> operation(key, if (first) initialValueSelector(key, e) else value as R, e) } + + +/** + * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] + * starting with the [initialValue]. + * + * @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 { k, v, e, first -> operation(if (first) initialValue else v as R, e) } + +/** + * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] + * starting with the [initialValue] 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 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) { k, v, e, first -> operation(if (first) initialValue else v as R, e) } + + +/** + * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] + * starting the first element in that group. + * + * @param operation a function that is invoked on each subsequent element of the group with the following parameters: + * - `key`: the key of a 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, value, e, first -> + if (first) e else operation(key, value as S, e) + } + +/** + * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] + * starting the first element in that 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 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, value, e, first -> + if (first) e else operation(key, value as S, e) + } + + +/** + * Groups elements from the [Grouping] source by key and counts elements in each group. + * + * @return a [Map] associating the key of each group with the count of element in the group. + */ +@SinceKotlin("1.1") +@JvmVersion +public fun Grouping.eachCount(): Map = + // fold(0) { acc, e -> acc + 1 } optimized for boxing + fold( + initialValueSelector = { k, e -> kotlin.jvm.internal.Ref.IntRef() }, + operation = { k, acc, e -> acc.apply { element += 1 } }) + .mapValues { it.value.element } + +/** + * Groups elements from the [Grouping] source by key and counts elements in each group to the given [destination] map. + * + * @return the [destination] map associating the key of each group with the count of element in the group. + */ +@SinceKotlin("1.1") +public fun > Grouping.eachCountTo(destination: M): M = + foldTo(destination, 0) { acc, e -> acc + 1 } + +/** + * Groups elements from the [Grouping] source by key and sums values provided by the [valueSelector] function for elements in each group. + * + * @return a [Map] associating the key of each group with the count of element in the group. + */ +@SinceKotlin("1.1") +@JvmVersion +public inline fun Grouping.eachSumOf(valueSelector: (T) -> Int): Map = + // fold(0) { acc, e -> acc + valueSelector(e)} optimized for boxing + fold( + initialValueSelector = { k, e -> kotlin.jvm.internal.Ref.IntRef() }, + operation = { k, acc, e -> acc.apply { element += valueSelector(e) } }) + .mapValues { it.value.element } + +/** + * Groups elements from the [Grouping] source by key and sums values provided by the [valueSelector] function for elements in each group + * to the given [destination] map. + * + * @return the [destination] map associating the key of each group with the count of element in the group. + */ +@SinceKotlin("1.1") +public inline fun > Grouping.eachSumOfTo(destination: M, valueSelector: (T) -> Int): M = + foldTo(destination, 0) { acc, e -> acc + valueSelector(e)} + + +/* +// TODO: sum by long and by double overloads + +public inline fun > Grouping.sumEachByLongTo(destination: M, valueSelector: (T) -> Long): M = + foldTo(destination, 0L) { acc, e -> acc + valueSelector(e)} + +public inline fun Grouping.sumEachByLong(valueSelector: (T) -> Long): Map = + fold(0L) { acc, e -> acc + valueSelector(e)} + +public inline fun > Grouping.sumEachByDoubleTo(destination: M, valueSelector: (T) -> Double): M = + foldTo(destination, 0.0) { acc, e -> acc + valueSelector(e)} + +public inline fun Grouping.sumEachByDouble(valueSelector: (T) -> Double): Map = + fold(0.0) { acc, e -> acc + valueSelector(e)} +*/ diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt index 6077db3b5fbda..c01df13a4c837 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt @@ -662,6 +662,7 @@ public final class kotlin/collections/ArraysKt { public static final fun groupByTo ([SLjava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo ([ZLjava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo ([ZLjava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun groupingBy ([Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlin/collections/Grouping; public static final fun indexOf ([BB)I public static final fun indexOf ([CC)I public static final fun indexOf ([DD)I @@ -1465,6 +1466,7 @@ public final class kotlin/collections/CollectionsKt { public static final fun groupBy (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo (Ljava/lang/Iterable;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo (Ljava/lang/Iterable;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun groupingBy (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Lkotlin/collections/Grouping; public static final fun indexOf (Ljava/lang/Iterable;Ljava/lang/Object;)I public static final fun indexOf (Ljava/util/List;Ljava/lang/Object;)I public static final fun indexOfFirst (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)I @@ -1598,6 +1600,26 @@ public final class kotlin/collections/CollectionsKt { public static final fun zip (Ljava/lang/Iterable;[Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/util/List; } +public abstract interface class kotlin/collections/Grouping { + public abstract fun elementIterator ()Ljava/util/Iterator; + public abstract fun keyOf (Ljava/lang/Object;)Ljava/lang/Object; +} + +public final class kotlin/collections/GroupingKt { + public static final fun aggregate (Lkotlin/collections/Grouping;Lkotlin/jvm/functions/Function4;)Ljava/util/Map; + public static final fun aggregateTo (Lkotlin/collections/Grouping;Ljava/util/Map;Lkotlin/jvm/functions/Function4;)Ljava/util/Map; + public static final fun eachCount (Lkotlin/collections/Grouping;)Ljava/util/Map; + public static final fun eachCountTo (Lkotlin/collections/Grouping;Ljava/util/Map;)Ljava/util/Map; + public static final fun eachSumOf (Lkotlin/collections/Grouping;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun eachSumOfTo (Lkotlin/collections/Grouping;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun fold (Lkotlin/collections/Grouping;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/util/Map; + public static final fun fold (Lkotlin/collections/Grouping;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/util/Map; + public static final fun foldTo (Lkotlin/collections/Grouping;Ljava/util/Map;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/util/Map; + public static final fun foldTo (Lkotlin/collections/Grouping;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/util/Map; + public static final fun reduce (Lkotlin/collections/Grouping;Lkotlin/jvm/functions/Function3;)Ljava/util/Map; + public static final fun reduceTo (Lkotlin/collections/Grouping;Ljava/util/Map;Lkotlin/jvm/functions/Function3;)Ljava/util/Map; +} + public final class kotlin/collections/IndexedValue { public fun (ILjava/lang/Object;)V public final fun component1 ()I @@ -2063,6 +2085,7 @@ public final class kotlin/sequences/SequencesKt { public static final fun groupBy (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo (Lkotlin/sequences/Sequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo (Lkotlin/sequences/Sequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun groupingBy (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Lkotlin/collections/Grouping; public static final fun indexOf (Lkotlin/sequences/Sequence;Ljava/lang/Object;)I public static final fun indexOfFirst (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)I public static final fun indexOfLast (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)I @@ -2402,6 +2425,7 @@ public final class kotlin/text/StringsKt { public static final fun groupBy (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo (Ljava/lang/CharSequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun groupByTo (Ljava/lang/CharSequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun groupingBy (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)Lkotlin/collections/Grouping; public static final fun hasSurrogatePairAt (Ljava/lang/CharSequence;I)Z public static final fun indexOf (Ljava/lang/CharSequence;CIZ)I public static final fun indexOf (Ljava/lang/CharSequence;Ljava/lang/String;IZ)I diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt index 3c1ecaf3c94c0..c8041b24f17ec 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt @@ -388,5 +388,32 @@ fun mapping(): List { } } + templates add f("groupingBy(crossinline keySelector: (T) -> K)") { + since("1.1") + inline(true) + only(Iterables, Sequences, ArraysOfObjects, CharSequences) + + typeParam("T") + typeParam("K") + + returns("Grouping") + + doc { f -> + """ + Creates a [Grouping] source from ${f.collection.prefixWithArticle()} to be used later with one of group-and-fold operations + using the specified [keySelector] function to extract a key from each ${f.element}. + """ + } + + body { + """ + return object : Grouping { + override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun keyOf(element: T): K = keySelector(element) + } + """ + } + } + return templates } \ No newline at end of file From 16bff4c8efb2ceb0471dffc3d66ab89abf8eda83 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Wed, 28 Dec 2016 17:50:22 +0300 Subject: [PATCH 2/5] Tests for group-and-fold operations --- .../stdlib/test/collections/GroupingTest.kt | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 libraries/stdlib/test/collections/GroupingTest.kt diff --git a/libraries/stdlib/test/collections/GroupingTest.kt b/libraries/stdlib/test/collections/GroupingTest.kt new file mode 100644 index 0000000000000..e32aee1123863 --- /dev/null +++ b/libraries/stdlib/test/collections/GroupingTest.kt @@ -0,0 +1,113 @@ +package test.collections + +import org.junit.Test +import java.util.* +import kotlin.test.* + +class GroupingTest { + + @Test fun groupingProducers() { + + fun verifyGrouping(grouping: Grouping, expectedElements: List, expectedKeys: List) { + val elements = grouping.elementIterator().asSequence().toList() + val keys = elements.map { grouping.keyOf(it) } // TODO: replace with grouping::keyOf when supported in JS + + assertEquals(expectedElements, elements) + assertEquals(expectedKeys, keys) + } + + val elements = listOf("foo", "bar", "value", "x") + val keySelector: (String) -> Int = { it.length } + val keys = elements.map(keySelector) + + fun verifyGrouping(grouping: Grouping) = verifyGrouping(grouping, elements, keys) + + verifyGrouping(elements.groupingBy { it.length }) + verifyGrouping(elements.toTypedArray().groupingBy(keySelector)) + verifyGrouping(elements.asSequence().groupingBy(keySelector)) + + val charSeq = "some sequence of chars" + verifyGrouping(charSeq.groupingBy { it.toInt() }, charSeq.toList(), charSeq.map { it.toInt() }) + } + + // aggregate and aggregateTo operations are not tested, but they're used in every other operation + + + @Test fun foldWithConstantInitialValue() { + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + // only collect strings with even length + val result = elements.groupingBy { it.first() }.fold(listOf()) { acc, e -> if (e.length % 2 == 0) acc + e else acc } + + assertEquals(mapOf('f' to listOf("flea"), 'b' to emptyList(), 'z' to emptyList()), result) + + val moreElements = listOf("fire", "zero", "abstract") + val result2 = moreElements.groupingBy { it.first() }.foldTo(HashMap(result), listOf()) { acc, e -> if (e.length % 2 == 0) acc + e else acc } + + assertEquals(mapOf('f' to listOf("flea", "fire"), 'b' to emptyList(), 'z' to listOf("zero"), 'a' to listOf("abstract")), result2) + } + + data class Collector(val key: K, val values: MutableList = mutableListOf()) + + @Test fun foldWithComputedInitialValue() { + + fun Collector.accumulateIfEven(e: String) = apply { if (e.length % 2 == 0) values.add(e) } + fun Collector.toPair() = key to values as List + + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + val result = elements.groupingBy { it.first() } + .fold({ k, e -> Collector(k)}, { k, acc, e -> acc.accumulateIfEven(e) }) + + val ordered = result.values.sortedBy { it.key }.map { it.toPair() } + assertEquals(listOf('b' to emptyList(), 'f' to listOf("flea"), 'z' to emptyList()), ordered) + + val moreElements = listOf("fire", "zero") + val result2 = moreElements.groupingBy { it.first() } + .foldTo(HashMap(result), + { k, e -> error("should not be called for $k") }, + { k, acc, e -> acc.accumulateIfEven(e) }) + + val ordered2 = result2.values.sortedBy { it.key }.map { it.toPair() } + assertEquals(listOf('b' to emptyList(), 'f' to listOf("flea", "fire"), 'z' to listOf("zero")), ordered2) + } + + inline fun > maxOfBy(a: T, b: T, keySelector: (T) -> K) = if (keySelector(a) >= keySelector(b)) a else b + + @Test fun reduce() { + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + fun Char.isVowel() = this in "aeiou" + fun String.countVowels() = count(Char::isVowel) + val maxVowels = elements.groupingBy { it.first() }.reduce { k, a, b -> maxOfBy(a, b, String::countVowels) } + + assertEquals(mapOf('f' to "foo", 'b' to "biscuit", 'z' to "zoo"), maxVowels) + + val elements2 = listOf("bar", "z", "fork") + val concats = elements2.groupingBy { it.first() }.reduceTo(HashMap(maxVowels)) { k, acc, e -> acc + e } + + assertEquals(mapOf('f' to "foofork", 'b' to "biscuitbar", 'z' to "zooz"), concats) + } + + @Test fun countEach() { + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + val counts = elements.groupingBy { it.first() }.eachCount() + + assertEquals(mapOf('f' to 2, 'b' to 2, 'z' to 1), counts) + + val elements2 = arrayOf("zebra", "baz", "cab") + val counts2 = elements2.groupingBy { it.last() }.eachCountTo(HashMap(counts)) + + assertEquals(mapOf('f' to 2, 'b' to 3, 'a' to 1, 'z' to 2), counts2) + } + + + @Test fun sumEach() { + val values = listOf("k" to 50, "b" to 20, "k" to 1000 ) + val summary = values.groupingBy { it.first }.eachSumOf { it.second } + + assertEquals(mapOf("k" to 1050, "b" to 20), summary) + + val values2 = listOf("key", "ball", "builder", "alpha") + val summary2 = values2.groupingBy { it.first().toString() }.eachSumOfTo(HashMap(summary)) { it.length } + + assertEquals(mapOf("k" to 1053, "b" to 31, "a" to 5), summary2) + } +} \ No newline at end of file From 3327c3546cdc5e3e65d51e15b73028cc37190638 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 29 Dec 2016 19:04:03 +0300 Subject: [PATCH 3/5] Minor refactoring: use mapValuesInPlace optimization, delegate aggregate implementation to aggregateTo. --- .../stdlib/src/kotlin/collections/Grouping.kt | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/libraries/stdlib/src/kotlin/collections/Grouping.kt b/libraries/stdlib/src/kotlin/collections/Grouping.kt index d72aa4333b52c..58282872754a3 100644 --- a/libraries/stdlib/src/kotlin/collections/Grouping.kt +++ b/libraries/stdlib/src/kotlin/collections/Grouping.kt @@ -40,13 +40,7 @@ public interface Grouping { public inline fun Grouping.aggregate( operation: (key: K, value: R?, element: T, first: Boolean) -> R ): Map { - val result = mutableMapOf() - for (e in this.elementIterator()) { - val key = keyOf(e) - val value = result[key] - result[key] = operation(key, value, e, value == null && !result.containsKey(key)) - } - return result + return aggregateTo(mutableMapOf(), operation) } /** @@ -73,8 +67,8 @@ public inline fun > Grouping.aggregateTo( ): M { for (e in this.elementIterator()) { val key = keyOf(e) - val acc = destination[key] - destination[key] = operation(key, acc, e, acc == null && !destination.containsKey(key)) + val accumulator = destination[key] + destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key)) } return destination } @@ -222,10 +216,10 @@ public inline fun > Grouping.reduceTo @JvmVersion public fun Grouping.eachCount(): Map = // fold(0) { acc, e -> acc + 1 } optimized for boxing - fold( + foldTo( destination = mutableMapOf(), initialValueSelector = { k, e -> kotlin.jvm.internal.Ref.IntRef() }, operation = { k, acc, e -> acc.apply { element += 1 } }) - .mapValues { it.value.element } + .mapValuesInPlace { it.value.element } /** * Groups elements from the [Grouping] source by key and counts elements in each group to the given [destination] map. @@ -245,10 +239,10 @@ public fun > Grouping.eachCountTo(destinat @JvmVersion public inline fun Grouping.eachSumOf(valueSelector: (T) -> Int): Map = // fold(0) { acc, e -> acc + valueSelector(e)} optimized for boxing - fold( + foldTo( destination = mutableMapOf(), initialValueSelector = { k, e -> kotlin.jvm.internal.Ref.IntRef() }, operation = { k, acc, e -> acc.apply { element += valueSelector(e) } }) - .mapValues { it.value.element } + .mapValuesInPlace { it.value.element } /** * Groups elements from the [Grouping] source by key and sums values provided by the [valueSelector] function for elements in each group @@ -261,6 +255,16 @@ public inline fun > Grouping.eachSumOfTo(d foldTo(destination, 0) { acc, e -> acc + valueSelector(e)} +@JvmVersion +@PublishedApi +@kotlin.internal.InlineOnly +internal inline fun MutableMap.mapValuesInPlace(f: (Map.Entry) -> R): MutableMap { + entries.forEach { + (it as MutableMap.MutableEntry).setValue(f(it)) + } + return (this as MutableMap) +} + /* // TODO: sum by long and by double overloads From fed11aaae71208d25c27a9d75a551b0aff2b794d Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 29 Dec 2016 19:27:29 +0300 Subject: [PATCH 4/5] Rename Grouping.elementIterator to sourceIterator --- js/js.libraries/src/core/generated/_ArraysJs.kt | 2 +- js/js.libraries/src/core/generated/_CollectionsJs.kt | 2 +- js/js.libraries/src/core/generated/_SequencesJs.kt | 2 +- js/js.libraries/src/core/generated/_StringsJs.kt | 2 +- libraries/stdlib/src/generated/_Arrays.kt | 2 +- libraries/stdlib/src/generated/_Collections.kt | 2 +- libraries/stdlib/src/generated/_Sequences.kt | 2 +- libraries/stdlib/src/generated/_Strings.kt | 2 +- libraries/stdlib/src/kotlin/collections/Grouping.kt | 6 +++--- libraries/stdlib/test/collections/GroupingTest.kt | 2 +- .../reference-public-api/kotlin-stdlib.txt | 2 +- libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/js/js.libraries/src/core/generated/_ArraysJs.kt b/js/js.libraries/src/core/generated/_ArraysJs.kt index 1e8a66aeea7e0..04a94c872311c 100644 --- a/js/js.libraries/src/core/generated/_ArraysJs.kt +++ b/js/js.libraries/src/core/generated/_ArraysJs.kt @@ -7637,7 +7637,7 @@ public inline fun >> CharArray.groupBy @SinceKotlin("1.1") public inline fun Array.groupingBy(crossinline keySelector: (T) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: T): K = keySelector(element) } } diff --git a/js/js.libraries/src/core/generated/_CollectionsJs.kt b/js/js.libraries/src/core/generated/_CollectionsJs.kt index 65769d2c2ee78..8be6e4c28083b 100644 --- a/js/js.libraries/src/core/generated/_CollectionsJs.kt +++ b/js/js.libraries/src/core/generated/_CollectionsJs.kt @@ -1186,7 +1186,7 @@ public inline fun >> Iterable.gr @SinceKotlin("1.1") public inline fun Iterable.groupingBy(crossinline keySelector: (T) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: T): K = keySelector(element) } } diff --git a/js/js.libraries/src/core/generated/_SequencesJs.kt b/js/js.libraries/src/core/generated/_SequencesJs.kt index 2cdb0fdeb3803..3c63d9fc746b6 100644 --- a/js/js.libraries/src/core/generated/_SequencesJs.kt +++ b/js/js.libraries/src/core/generated/_SequencesJs.kt @@ -651,7 +651,7 @@ public inline fun >> Sequence.gr @SinceKotlin("1.1") public inline fun Sequence.groupingBy(crossinline keySelector: (T) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: T): K = keySelector(element) } } diff --git a/js/js.libraries/src/core/generated/_StringsJs.kt b/js/js.libraries/src/core/generated/_StringsJs.kt index 34ca23c00656e..b6d9503bea46c 100644 --- a/js/js.libraries/src/core/generated/_StringsJs.kt +++ b/js/js.libraries/src/core/generated/_StringsJs.kt @@ -708,7 +708,7 @@ public inline fun >> CharSequence.grou @SinceKotlin("1.1") public inline fun CharSequence.groupingBy(crossinline keySelector: (Char) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: Char): K = keySelector(element) } } diff --git a/libraries/stdlib/src/generated/_Arrays.kt b/libraries/stdlib/src/generated/_Arrays.kt index 9f5894eab7910..c80b29a6bd234 100644 --- a/libraries/stdlib/src/generated/_Arrays.kt +++ b/libraries/stdlib/src/generated/_Arrays.kt @@ -7720,7 +7720,7 @@ public inline fun >> CharArray.groupBy @SinceKotlin("1.1") public inline fun Array.groupingBy(crossinline keySelector: (T) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: T): K = keySelector(element) } } diff --git a/libraries/stdlib/src/generated/_Collections.kt b/libraries/stdlib/src/generated/_Collections.kt index 5cda36f3a1fcf..06443bc0f44bd 100644 --- a/libraries/stdlib/src/generated/_Collections.kt +++ b/libraries/stdlib/src/generated/_Collections.kt @@ -1197,7 +1197,7 @@ public inline fun >> Iterable.gr @SinceKotlin("1.1") public inline fun Iterable.groupingBy(crossinline keySelector: (T) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: T): K = keySelector(element) } } diff --git a/libraries/stdlib/src/generated/_Sequences.kt b/libraries/stdlib/src/generated/_Sequences.kt index fbdd73f675adf..dd336325bed15 100644 --- a/libraries/stdlib/src/generated/_Sequences.kt +++ b/libraries/stdlib/src/generated/_Sequences.kt @@ -670,7 +670,7 @@ public inline fun >> Sequence.gr @SinceKotlin("1.1") public inline fun Sequence.groupingBy(crossinline keySelector: (T) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: T): K = keySelector(element) } } diff --git a/libraries/stdlib/src/generated/_Strings.kt b/libraries/stdlib/src/generated/_Strings.kt index c02cdf081a0e0..e6c44fb72c19d 100644 --- a/libraries/stdlib/src/generated/_Strings.kt +++ b/libraries/stdlib/src/generated/_Strings.kt @@ -717,7 +717,7 @@ public inline fun >> CharSequence.grou @SinceKotlin("1.1") public inline fun CharSequence.groupingBy(crossinline keySelector: (Char) -> K): Grouping { return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: Char): K = keySelector(element) } } diff --git a/libraries/stdlib/src/kotlin/collections/Grouping.kt b/libraries/stdlib/src/kotlin/collections/Grouping.kt index 58282872754a3..760843edf7588 100644 --- a/libraries/stdlib/src/kotlin/collections/Grouping.kt +++ b/libraries/stdlib/src/kotlin/collections/Grouping.kt @@ -17,8 +17,8 @@ package kotlin.collections */ @SinceKotlin("1.1") public interface Grouping { - /** Returns an [Iterator] which iterates through the elements of the source. */ - fun elementIterator(): Iterator + /** 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 } @@ -65,7 +65,7 @@ public inline fun > Grouping.aggregateTo( destination: M, operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R ): M { - for (e in this.elementIterator()) { + for (e in this.sourceIterator()) { val key = keyOf(e) val accumulator = destination[key] destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key)) diff --git a/libraries/stdlib/test/collections/GroupingTest.kt b/libraries/stdlib/test/collections/GroupingTest.kt index e32aee1123863..8f1e257bb3aec 100644 --- a/libraries/stdlib/test/collections/GroupingTest.kt +++ b/libraries/stdlib/test/collections/GroupingTest.kt @@ -9,7 +9,7 @@ class GroupingTest { @Test fun groupingProducers() { fun verifyGrouping(grouping: Grouping, expectedElements: List, expectedKeys: List) { - val elements = grouping.elementIterator().asSequence().toList() + val elements = grouping.sourceIterator().asSequence().toList() val keys = elements.map { grouping.keyOf(it) } // TODO: replace with grouping::keyOf when supported in JS assertEquals(expectedElements, elements) diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt index c01df13a4c837..a319d3980d0ed 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib.txt @@ -1601,8 +1601,8 @@ public final class kotlin/collections/CollectionsKt { } public abstract interface class kotlin/collections/Grouping { - public abstract fun elementIterator ()Ljava/util/Iterator; public abstract fun keyOf (Ljava/lang/Object;)Ljava/lang/Object; + public abstract fun sourceIterator ()Ljava/util/Iterator; } public final class kotlin/collections/GroupingKt { diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt index c8041b24f17ec..6c1dda927a658 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Mapping.kt @@ -408,7 +408,7 @@ fun mapping(): List { body { """ return object : Grouping { - override fun elementIterator(): Iterator = this@groupingBy.iterator() + override fun sourceIterator(): Iterator = this@groupingBy.iterator() override fun keyOf(element: T): K = keySelector(element) } """ From e7d9b7944fb28226e6f25e971e1335c666d33f28 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 5 Jan 2017 08:41:27 +0300 Subject: [PATCH 5/5] Improve docs, use consistent naming for parameters, rename unused parameters of lambdas to _. --- .../stdlib/src/kotlin/collections/Grouping.kt | 114 ++++++++++-------- 1 file changed, 67 insertions(+), 47 deletions(-) diff --git a/libraries/stdlib/src/kotlin/collections/Grouping.kt b/libraries/stdlib/src/kotlin/collections/Grouping.kt index 760843edf7588..a5d01af454394 100644 --- a/libraries/stdlib/src/kotlin/collections/Grouping.kt +++ b/libraries/stdlib/src/kotlin/collections/Grouping.kt @@ -24,36 +24,38 @@ public interface Grouping { } /** - * Groups elements from the [Grouping] source by key and aggregates elements of each group with the specified [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 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 a group this element belongs to; - * - `value`: the current value of the accumulator of a group, can be `null` if it's first `element` encountered in the group; + * - `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 first `element` encountered in the group. + * - `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, value: R?, element: T, first: Boolean) -> R + operation: (key: K, accumulator: R?, element: T, first: Boolean) -> R ): Map { return aggregateTo(mutableMapOf(), operation) } /** - * Groups elements from the [Grouping] source by key and aggregates elements of each group with the specified [operation] - * to the given [destination] map. + * 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 a group this element belongs to; - * - `accumulator`: the current value of the accumulator of the group, can be `null` if it's first `element` encountered in the group; + * - `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 first `element` encountered in the group. + * - `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`. @@ -74,16 +76,17 @@ public inline fun > Grouping.aggregateTo( } /** - * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] - * starting with an initial value of accumulator provided by the [initialValueSelector] function. + * 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 an each group. + * @param initialValueSelector a function that provides an initial value of accumulator for each group. * It's invoked with parameters: - * - `key`: the key of a group; + * - `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 a group this element belongs to; + * - `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. * @@ -94,23 +97,24 @@ public inline fun Grouping.fold( initialValueSelector: (key: K, element: T) -> R, operation: (key: K, accumulator: R, element: T) -> R ): Map = - aggregate { key, value, e, first -> operation(key, if (first) initialValueSelector(key, e) else value as R, e) } + 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 accumulates elements of each group with the specified [operation] - * starting with an initial value of accumulator provided by the [initialValueSelector] function - * to the given [destination] map. + * 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 an each group. + * @param initialValueSelector a function that provides an initial value of accumulator for each group. * It's invoked with parameters: - * - `key`: the key of a group; + * - `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 a group this element belongs to; + * - `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. * @@ -122,12 +126,13 @@ public inline fun > Grouping.foldTo( initialValueSelector: (key: K, element: T) -> R, operation: (key: K, accumulator: R, element: T) -> R ): M = - aggregateTo(destination) { key, value, e, first -> operation(key, if (first) initialValueSelector(key, e) else value as R, e) } + 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 accumulates elements of each group with the specified [operation] - * starting with the [initialValue]. + * 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; @@ -140,11 +145,13 @@ public inline fun Grouping.fold( initialValue: R, operation: (accumulator: R, element: T) -> R ): Map = - aggregate { k, v, e, first -> operation(if (first) initialValue else v as R, e) } + aggregate { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } /** - * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] - * starting with the [initialValue] to the given [destination] map. + * 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. @@ -161,15 +168,18 @@ public inline fun > Grouping.foldTo( initialValue: R, operation: (accumulator: R, element: T) -> R ): M = - aggregateTo(destination) { k, v, e, first -> operation(if (first) initialValue else v as R, e) } + aggregateTo(destination) { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } /** - * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] - * starting the first element in that group. + * 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 a group this element belongs to; + * - `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. * @@ -179,13 +189,16 @@ public inline fun > Grouping.foldTo( public inline fun Grouping.reduce( operation: (key: K, accumulator: S, element: T) -> S ): Map = - aggregate { key, value, e, first -> - if (first) e else operation(key, value as S, e) + aggregate { key, acc, e, first -> + if (first) e else operation(key, acc as S, e) } /** - * Groups elements from the [Grouping] source by key and accumulates elements of each group with the specified [operation] - * starting the first element in that group to the given [destination] map. + * 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 @@ -202,53 +215,60 @@ public inline fun > Grouping.reduceTo destination: M, operation: (key: K, accumulator: S, element: T) -> S ): M = - aggregateTo(destination) { key, value, e, first -> - if (first) e else operation(key, value as S, e) + 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. * - * @return a [Map] associating the key of each group with the count of element in the group. + * @return a [Map] associating the key of each group with the count of elements in the group. */ @SinceKotlin("1.1") @JvmVersion public fun Grouping.eachCount(): Map = // fold(0) { acc, e -> acc + 1 } optimized for boxing foldTo( destination = mutableMapOf(), - initialValueSelector = { k, e -> kotlin.jvm.internal.Ref.IntRef() }, - operation = { k, acc, e -> acc.apply { element += 1 } }) + initialValueSelector = { _, _ -> kotlin.jvm.internal.Ref.IntRef() }, + operation = { _, acc, _ -> acc.apply { element += 1 } }) .mapValuesInPlace { it.value.element } /** * Groups elements from the [Grouping] source by key and counts elements in each group to the given [destination] map. * - * @return the [destination] map associating the key of each group with the count of element in 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 counter for that group. + * + * @return the [destination] map associating the key of each group with the count of elements in the group. */ @SinceKotlin("1.1") public fun > Grouping.eachCountTo(destination: M): M = - foldTo(destination, 0) { acc, e -> acc + 1 } + foldTo(destination, 0) { acc, _ -> acc + 1 } /** * Groups elements from the [Grouping] source by key and sums values provided by the [valueSelector] function for elements in each group. * - * @return a [Map] associating the key of each group with the count of element in the group. + * @return a [Map] associating the key of each group with the sum of elements in the group. */ @SinceKotlin("1.1") @JvmVersion public inline fun Grouping.eachSumOf(valueSelector: (T) -> Int): Map = // fold(0) { acc, e -> acc + valueSelector(e)} optimized for boxing foldTo( destination = mutableMapOf(), - initialValueSelector = { k, e -> kotlin.jvm.internal.Ref.IntRef() }, - operation = { k, acc, e -> acc.apply { element += valueSelector(e) } }) + initialValueSelector = { _, _ -> kotlin.jvm.internal.Ref.IntRef() }, + operation = { _, acc, e -> acc.apply { element += valueSelector(e) } }) .mapValuesInPlace { it.value.element } /** * Groups elements from the [Grouping] source by key and sums values provided by the [valueSelector] function for elements in each group * to the given [destination] map. * - * @return the [destination] map associating the key of each group with the count of element in 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 sum for that group. + * + * @return the [destination] map associating the key of each group with the sum of elements in the group. */ @SinceKotlin("1.1") public inline fun > Grouping.eachSumOfTo(destination: M, valueSelector: (T) -> Int): M =