From f36732208457368d8eb64cb19bef0b07129c1dbf Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Mon, 20 Aug 2018 22:01:12 +0300 Subject: [PATCH] Introduce associateWith and associateWithTo functions #KT-13814 --- .../common/src/generated/_Collections.kt | 30 +++++++++ .../stdlib/common/src/generated/_Sequences.kt | 34 ++++++++++ .../stdlib/common/src/generated/_Strings.kt | 30 +++++++++ .../test/samples/collections/collections.kt | 8 +++ .../samples/test/samples/text/strings.kt | 9 +++ .../stdlib/test/collections/CollectionTest.kt | 12 ++++ .../stdlib/test/collections/SequenceTest.kt | 12 ++++ libraries/stdlib/test/text/StringTest.kt | 8 +++ .../kotlin-stdlib-runtime-merged.txt | 6 ++ .../src/templates/Snapshots.kt | 62 +++++++++++++++++++ 10 files changed, 211 insertions(+) diff --git a/libraries/stdlib/common/src/generated/_Collections.kt b/libraries/stdlib/common/src/generated/_Collections.kt index c6fcc5f34ad..8ef7760b5d6 100644 --- a/libraries/stdlib/common/src/generated/_Collections.kt +++ b/libraries/stdlib/common/src/generated/_Collections.kt @@ -1059,6 +1059,36 @@ public inline fun > Iterable.associateTo( return destination } +/** + * Returns a [Map] where keys are elements from the given collection and values are + * produced by the [valueSelector] function applied to each element. + * + * If any two elements are equal, the last one gets added to the map. + * + * The returned map preserves the entry iteration order of the original collection. + * + * @sample samples.collections.Collections.Transformations.associateWith + */ +@SinceKotlin("1.3") +public inline fun Iterable.associateWith(valueSelector: (K) -> V): Map { + val result = LinkedHashMap(mapCapacity(collectionSizeOrDefault(10)).coerceAtLeast(16)) + return associateWithTo(result, valueSelector) +} + +/** + * Populates and returns the [destination] mutable map with key-value pairs for each element of the given collection, + * where key is the element itself and value is provided by the [valueSelector] function applied to that key. + * + * If any two elements are equal, the last one overwrites the former value in the map. + */ +@SinceKotlin("1.3") +public inline fun > Iterable.associateWithTo(destination: M, valueSelector: (K) -> V): M { + for (element in this) { + destination.put(element, valueSelector(element)) + } + return destination +} + /** * Appends all elements to the given [destination] collection. */ diff --git a/libraries/stdlib/common/src/generated/_Sequences.kt b/libraries/stdlib/common/src/generated/_Sequences.kt index 89770b5b7f9..96d536d1f43 100644 --- a/libraries/stdlib/common/src/generated/_Sequences.kt +++ b/libraries/stdlib/common/src/generated/_Sequences.kt @@ -642,6 +642,40 @@ public inline fun > Sequence.associateTo( return destination } +/** + * Returns a [Map] where keys are elements from the given sequence and values are + * produced by the [valueSelector] function applied to each element. + * + * If any two elements are equal, the last one gets added to the map. + * + * The returned map preserves the entry iteration order of the original sequence. + * + * The operation is _terminal_. + * + * @sample samples.collections.Collections.Transformations.associateWith + */ +@SinceKotlin("1.3") +public inline fun Sequence.associateWith(valueSelector: (K) -> V): Map { + val result = LinkedHashMap() + return associateWithTo(result, valueSelector) +} + +/** + * Populates and returns the [destination] mutable map with key-value pairs for each element of the given sequence, + * where key is the element itself and value is provided by the [valueSelector] function applied to that key. + * + * If any two elements are equal, the last one overwrites the former value in the map. + * + * The operation is _terminal_. + */ +@SinceKotlin("1.3") +public inline fun > Sequence.associateWithTo(destination: M, valueSelector: (K) -> V): M { + for (element in this) { + destination.put(element, valueSelector(element)) + } + return destination +} + /** * Appends all elements to the given [destination] collection. * diff --git a/libraries/stdlib/common/src/generated/_Strings.kt b/libraries/stdlib/common/src/generated/_Strings.kt index c9adef6aa0f..8aa32fb2f1b 100644 --- a/libraries/stdlib/common/src/generated/_Strings.kt +++ b/libraries/stdlib/common/src/generated/_Strings.kt @@ -615,6 +615,36 @@ public inline fun > CharSequence.associateTo(de return destination } +/** + * Returns a [Map] where keys are characters from the given char sequence and values are + * produced by the [valueSelector] function applied to each character. + * + * If any two characters are equal, the last one gets added to the map. + * + * The returned map preserves the entry iteration order of the original char sequence. + * + * @sample samples.text.Strings.associateWith + */ +@SinceKotlin("1.3") +public inline fun CharSequence.associateWith(valueSelector: (Char) -> V): Map { + val result = LinkedHashMap(mapCapacity(length).coerceAtLeast(16)) + return associateWithTo(result, valueSelector) +} + +/** + * Populates and returns the [destination] mutable map with key-value pairs for each character of the given char sequence, + * where key is the character itself and value is provided by the [valueSelector] function applied to that key. + * + * If any two characters are equal, the last one overwrites the former value in the map. + */ +@SinceKotlin("1.3") +public inline fun > CharSequence.associateWithTo(destination: M, valueSelector: (Char) -> V): M { + for (element in this) { + destination.put(element, valueSelector(element)) + } + return destination +} + /** * Appends all characters to the given [destination] collection. */ diff --git a/libraries/stdlib/samples/test/samples/collections/collections.kt b/libraries/stdlib/samples/test/samples/collections/collections.kt index fe68b8b1545..81c4b2a1f61 100644 --- a/libraries/stdlib/samples/test/samples/collections/collections.kt +++ b/libraries/stdlib/samples/test/samples/collections/collections.kt @@ -300,6 +300,14 @@ class Collections { class Transformations { + @Sample + fun associateWith() { + val words = listOf("a", "abc", "ab", "def", "abcd") + val withLength = words.associateWith { it.length } + assertPrints(withLength.keys, "[a, abc, ab, def, abcd]") + assertPrints(withLength.values, "[1, 3, 2, 3, 4]") + } + @Sample fun groupBy() { val words = listOf("a", "abc", "ab", "def", "abcd") diff --git a/libraries/stdlib/samples/test/samples/text/strings.kt b/libraries/stdlib/samples/test/samples/text/strings.kt index 961619d6579..c081705153c 100644 --- a/libraries/stdlib/samples/test/samples/text/strings.kt +++ b/libraries/stdlib/samples/test/samples/text/strings.kt @@ -84,6 +84,15 @@ class Strings { assertPrints(result, "[az, by, cx]") } + @Sample + fun associateWith() { + val string = "bonne journée" + // associate each character with its code + val result = string.associateWith { char -> char.toInt() } + // notice each letter occurs only once + assertPrints(result, "{b=98, o=111, n=110, e=101, =32, j=106, u=117, r=114, é=233}") + } + @Sample fun stringToByteArray() { val charset = Charsets.UTF_8 diff --git a/libraries/stdlib/test/collections/CollectionTest.kt b/libraries/stdlib/test/collections/CollectionTest.kt index 271fb071442..748d7e876c5 100644 --- a/libraries/stdlib/test/collections/CollectionTest.kt +++ b/libraries/stdlib/test/collections/CollectionTest.kt @@ -358,6 +358,18 @@ class CollectionTest { assertEquals(namesByTeam, mutableNamesByTeam) } + @Test fun associateWith() { + val items = listOf("Alice", "Bob", "Carol") + val itemsWithTheirLength = items.associateWith { it.length } + + assertEquals(mapOf("Alice" to 5, "Bob" to 3, "Carol" to 5), itemsWithTheirLength) + + val updatedLength = + items.drop(1).associateWithTo(itemsWithTheirLength.toMutableMap()) { name -> name.toLowerCase().count { it in "aeuio" }} + + assertEquals(mapOf("Alice" to 5, "Bob" to 1, "Carol" to 2), updatedLength) + } + @Test fun plusRanges() { val range1 = 1..3 val range2 = 4..7 diff --git a/libraries/stdlib/test/collections/SequenceTest.kt b/libraries/stdlib/test/collections/SequenceTest.kt index b740931ff46..b26702d5de0 100644 --- a/libraries/stdlib/test/collections/SequenceTest.kt +++ b/libraries/stdlib/test/collections/SequenceTest.kt @@ -548,6 +548,18 @@ public class SequenceTest { assertEquals(listOf("act", "wast", "test"), sequenceOf("act", "test", "wast").sortedWith(comparator).toList()) } + @Test fun associateWith() { + val items = sequenceOf("Alice", "Bob", "Carol") + val itemsWithTheirLength = items.associateWith { it.length } + + assertEquals(mapOf("Alice" to 5, "Bob" to 3, "Carol" to 5), itemsWithTheirLength) + + val updatedLength = + items.drop(1).associateWithTo(itemsWithTheirLength.toMutableMap()) { name -> name.toLowerCase().count { it in "aeuio" }} + + assertEquals(mapOf("Alice" to 5, "Bob" to 1, "Carol" to 2), updatedLength) + } + @Test fun orEmpty() { val s1: Sequence? = null assertEquals(emptySequence(), s1.orEmpty()) diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index 3b98bb40287..861bf9c9b66 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -1159,6 +1159,14 @@ class StringTest { assertEquals(listOf('A', 'A', 'B', 'D'), result[true]) } + @Test fun associateWith() = withOneCharSequenceArg("abc") { data -> + val result = data.associateWith { it + 1 } + assertEquals(mapOf('a' to 'b', 'b' to 'c', 'c' to 'd'), result) + + val mutableResult = data.drop(1).associateWithTo(result.toMutableMap()) { it - 1 } + assertEquals(mapOf('a' to 'b', 'b' to 'a', 'c' to 'b'), mutableResult) + } + @Test fun joinToString() { val data = "abcd".toList() val result = data.joinToString("_", "(", ")") diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index b0c5378a08a..f3db473fdf7 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -2084,6 +2084,8 @@ public final class kotlin/collections/CollectionsKt { public static final fun associateByTo (Ljava/lang/Iterable;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun associateByTo (Ljava/lang/Iterable;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun associateTo (Ljava/lang/Iterable;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun associateWith (Ljava/lang/Iterable;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun associateWithTo (Ljava/lang/Iterable;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun averageOfByte (Ljava/lang/Iterable;)D public static final fun averageOfDouble (Ljava/lang/Iterable;)D public static final fun averageOfFloat (Ljava/lang/Iterable;)D @@ -4356,6 +4358,8 @@ public final class kotlin/sequences/SequencesKt { public static final fun associateByTo (Lkotlin/sequences/Sequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun associateByTo (Lkotlin/sequences/Sequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun associateTo (Lkotlin/sequences/Sequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun associateWith (Lkotlin/sequences/Sequence;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun associateWithTo (Lkotlin/sequences/Sequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun averageOfByte (Lkotlin/sequences/Sequence;)D public static final fun averageOfDouble (Lkotlin/sequences/Sequence;)D public static final fun averageOfFloat (Lkotlin/sequences/Sequence;)D @@ -4693,6 +4697,8 @@ public final class kotlin/text/StringsKt { public static final fun associateByTo (Ljava/lang/CharSequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun associateByTo (Ljava/lang/CharSequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun associateTo (Ljava/lang/CharSequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun associateWith (Ljava/lang/CharSequence;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; + public static final fun associateWithTo (Ljava/lang/CharSequence;Ljava/util/Map;Lkotlin/jvm/functions/Function1;)Ljava/util/Map; public static final fun capitalize (Ljava/lang/String;)Ljava/lang/String; public static final fun chunked (Ljava/lang/CharSequence;I)Ljava/util/List; public static final fun chunked (Ljava/lang/CharSequence;ILkotlin/jvm/functions/Function1;)Ljava/util/List; diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt index 4cffbe05e6a..4eb0528185c 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Snapshots.kt @@ -405,4 +405,66 @@ object Snapshots : TemplateGroupBase() { """ } } + + val f_associateWith = fn("associateWith(valueSelector: (K) -> V)") { + include(Iterables, Sequences, CharSequences) + } builder { + inline() + since("1.3") + typeParam("K", primary = true) + typeParam("V") + returns("Map") + doc { + """ + Returns a [Map] where keys are ${f.element.pluralize()} from the given ${f.collection} and values are + produced by the [valueSelector] function applied to each ${f.element}. + + If any two ${f.element.pluralize()} are equal, the last one gets added to the map. + + The returned map preserves the entry iteration order of the original ${f.collection}. + """ + } + sample(when (family) { + CharSequences -> "samples.text.Strings.associateWith" + else -> "samples.collections.Collections.Transformations.associateWith" + }) + body { + val resultMap = when (family) { + Iterables -> "LinkedHashMap(mapCapacity(collectionSizeOrDefault(10)).coerceAtLeast(16))" + CharSequences -> "LinkedHashMap(mapCapacity(length).coerceAtLeast(16))" + else -> "LinkedHashMap()" + } + """ + val result = $resultMap + return associateWithTo(result, valueSelector) + """ + } + } + + val f_associateWithTo = fn("associateWithTo(destination: M, valueSelector: (K) -> V)") { + include(Iterables, Sequences, CharSequences) + } builder { + inline() + since("1.3") + typeParam("K", primary = true) + typeParam("V") + typeParam("M : MutableMap") + returns("M") + doc { + """ + Populates and returns the [destination] mutable map with key-value pairs for each ${f.element} of the given ${f.collection}, + where key is the ${f.element} itself and value is provided by the [valueSelector] function applied to that key. + + If any two ${f.element.pluralize()} are equal, the last one overwrites the former value in the map. + """ + } + body { + """ + for (element in this) { + destination.put(element, valueSelector(element)) + } + return destination + """ + } + } }