diff --git a/libraries/stdlib/common/src/generated/_Arrays.kt b/libraries/stdlib/common/src/generated/_Arrays.kt index 13a2f4fdfbb..ace2514434d 100644 --- a/libraries/stdlib/common/src/generated/_Arrays.kt +++ b/libraries/stdlib/common/src/generated/_Arrays.kt @@ -12731,6 +12731,141 @@ public inline fun CharArray.reduceIndexed(operation: (index: Int, acc: Char, Cha return accumulator } +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun Array.reduceOrNull(operation: (acc: S, T) -> S): S? { + if (isEmpty()) + return null + var accumulator: S = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun ByteArray.reduceOrNull(operation: (acc: Byte, Byte) -> Byte): Byte? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun ShortArray.reduceOrNull(operation: (acc: Short, Short) -> Short): Short? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun IntArray.reduceOrNull(operation: (acc: Int, Int) -> Int): Int? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun LongArray.reduceOrNull(operation: (acc: Long, Long) -> Long): Long? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun FloatArray.reduceOrNull(operation: (acc: Float, Float) -> Float): Float? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun DoubleArray.reduceOrNull(operation: (acc: Double, Double) -> Double): Double? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun BooleanArray.reduceOrNull(operation: (acc: Boolean, Boolean) -> Boolean): Boolean? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun CharArray.reduceOrNull(operation: (acc: Char, Char) -> Char): Char? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + /** * Accumulates value starting with last element and applying [operation] from right to left to each element and current accumulator value. */ diff --git a/libraries/stdlib/common/src/generated/_Collections.kt b/libraries/stdlib/common/src/generated/_Collections.kt index a28298d269c..4d53244c2bf 100644 --- a/libraries/stdlib/common/src/generated/_Collections.kt +++ b/libraries/stdlib/common/src/generated/_Collections.kt @@ -1866,6 +1866,21 @@ public inline fun Iterable.reduceIndexed(operation: (index: Int, a return accumulator } +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the collection is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun Iterable.reduceOrNull(operation: (acc: S, T) -> S): S? { + val iterator = this.iterator() + if (!iterator.hasNext()) return null + var accumulator: S = iterator.next() + while (iterator.hasNext()) { + accumulator = operation(accumulator, iterator.next()) + } + return accumulator +} + /** * Accumulates value starting with last element and applying [operation] from right to left to each element and current accumulator value. */ diff --git a/libraries/stdlib/common/src/generated/_Sequences.kt b/libraries/stdlib/common/src/generated/_Sequences.kt index 78826302169..dab5480f9ff 100644 --- a/libraries/stdlib/common/src/generated/_Sequences.kt +++ b/libraries/stdlib/common/src/generated/_Sequences.kt @@ -1382,6 +1382,23 @@ public inline fun Sequence.reduceIndexed(operation: (index: Int, a return accumulator } +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the sequence is empty. + * + * The operation is _terminal_. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun Sequence.reduceOrNull(operation: (acc: S, T) -> S): S? { + val iterator = this.iterator() + if (!iterator.hasNext()) return null + var accumulator: S = iterator.next() + while (iterator.hasNext()) { + accumulator = operation(accumulator, iterator.next()) + } + return accumulator +} + /** * Returns the sum of all values produced by [selector] function applied to each element in the sequence. * diff --git a/libraries/stdlib/common/src/generated/_Strings.kt b/libraries/stdlib/common/src/generated/_Strings.kt index 0ccbd23fc15..feeddc3917f 100644 --- a/libraries/stdlib/common/src/generated/_Strings.kt +++ b/libraries/stdlib/common/src/generated/_Strings.kt @@ -1195,6 +1195,21 @@ public inline fun CharSequence.reduceIndexed(operation: (index: Int, acc: Char, return accumulator } +/** + * Accumulates value starting with the first character and applying [operation] from left to right to current accumulator value and each character. Returns null if the char sequence is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public inline fun CharSequence.reduceOrNull(operation: (acc: Char, Char) -> Char): Char? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + /** * Accumulates value starting with last character and applying [operation] from right to left to each character and current accumulator value. */ diff --git a/libraries/stdlib/common/src/generated/_UArrays.kt b/libraries/stdlib/common/src/generated/_UArrays.kt index fe2e150ee71..52d49a9bda8 100644 --- a/libraries/stdlib/common/src/generated/_UArrays.kt +++ b/libraries/stdlib/common/src/generated/_UArrays.kt @@ -5618,6 +5618,74 @@ public inline fun UShortArray.reduceIndexed(operation: (index: Int, acc: UShort, return accumulator } +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@ExperimentalUnsignedTypes +@kotlin.internal.InlineOnly +public inline fun UIntArray.reduceOrNull(operation: (acc: UInt, UInt) -> UInt): UInt? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@ExperimentalUnsignedTypes +@kotlin.internal.InlineOnly +public inline fun ULongArray.reduceOrNull(operation: (acc: ULong, ULong) -> ULong): ULong? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@ExperimentalUnsignedTypes +@kotlin.internal.InlineOnly +public inline fun UByteArray.reduceOrNull(operation: (acc: UByte, UByte) -> UByte): UByte? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + +/** + * Accumulates value starting with the first element and applying [operation] from left to right to current accumulator value and each element. Returns null if the array is empty. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@ExperimentalUnsignedTypes +@kotlin.internal.InlineOnly +public inline fun UShortArray.reduceOrNull(operation: (acc: UShort, UShort) -> UShort): UShort? { + if (isEmpty()) + return null + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator +} + /** * Accumulates value starting with last element and applying [operation] from right to left to each element and current accumulator value. */ diff --git a/libraries/stdlib/test/collections/ArraysTest.kt b/libraries/stdlib/test/collections/ArraysTest.kt index a334892cf79..086eb7d0936 100644 --- a/libraries/stdlib/test/collections/ArraysTest.kt +++ b/libraries/stdlib/test/collections/ArraysTest.kt @@ -1006,6 +1006,20 @@ class ArraysTest { } } + @Test fun reduceOrNull() { + expect(-4) { intArrayOf(1, 2, 3).reduceOrNull { a, b -> a - b } } + expect(-4.toLong()) { longArrayOf(1, 2, 3).reduceOrNull { a, b -> a - b } } + expect(-4F) { floatArrayOf(1F, 2F, 3F).reduceOrNull { a, b -> a - b } } + expect(-4.0) { doubleArrayOf(1.0, 2.0, 3.0).reduceOrNull { a, b -> a - b } } + expect('3') { charArrayOf('1', '3', '2').reduceOrNull { a, b -> if (a > b) a else b } } + expect(false) { booleanArrayOf(true, true, false).reduceOrNull { a, b -> a && b } } + expect(true) { booleanArrayOf(true, true).reduceOrNull { a, b -> a && b } } + expect(0.toByte()) { byteArrayOf(3, 2, 1).reduceOrNull { a, b -> (a - b).toByte() } } + expect(0.toShort()) { shortArrayOf(3, 2, 1).reduceOrNull { a, b -> (a - b).toShort() } } + + expect(null, { intArrayOf().reduceOrNull { a, b -> a + b } }) + } + @Test fun reduceRight() { expect(2) { intArrayOf(1, 2, 3).reduceRight { a, b -> a - b } } expect(2.toLong()) { longArrayOf(1, 2, 3).reduceRight { a, b -> a - b } } diff --git a/libraries/stdlib/test/collections/CollectionTest.kt b/libraries/stdlib/test/collections/CollectionTest.kt index 63fd6cee11e..c63307f7102 100644 --- a/libraries/stdlib/test/collections/CollectionTest.kt +++ b/libraries/stdlib/test/collections/CollectionTest.kt @@ -315,6 +315,15 @@ class CollectionTest { } } + @Test fun reduceOrNull() { + expect("1234") { + val list = listOf("1", "2", "3", "4") + list.reduceOrNull { a, b -> a + b } + } + + expect(null, { arrayListOf().reduceOrNull { a, b -> a + b } }) + } + @Test fun reduceRight() { expect("1234") { val list = listOf("1", "2", "3", "4") diff --git a/libraries/stdlib/test/collections/SequenceTest.kt b/libraries/stdlib/test/collections/SequenceTest.kt index 4f5555ded92..8f22aaa9e3f 100644 --- a/libraries/stdlib/test/collections/SequenceTest.kt +++ b/libraries/stdlib/test/collections/SequenceTest.kt @@ -379,6 +379,10 @@ public class SequenceTest { assertEquals(expected_, b.toList()) } + @Test fun reduceOrNullOnEmpty() { + expect(null, { sequenceOf().reduceOrNull { acc, i -> acc + i } }) + } + @Test fun minusElement() = testMinus(expected = listOf("foo", "bar")) { it - "bar" - "zoo" } @Test fun minusCollection() = testMinus { it - listOf("bar", "zoo") } @Test fun minusArray() = testMinus { it - arrayOf("bar", "zoo") } diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index 9864bbdef76..c25150ed031 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -1281,6 +1281,13 @@ class StringTest { } } + @Test fun reduceOrNull() = withOneCharSequenceArg { arg1 -> + // get the smallest character(by char value) + assertEquals('a', arg1("bacfd").reduceOrNull { v, c -> if (v > c) c else v }) + + expect(null, { arg1("").reduceOrNull { _, _ -> '\n' } }) + } + @Test fun groupBy() = withOneCharSequenceArg("abAbaABcD") { data -> // group characters by their case val result = data.groupBy { it.isAsciiUpperCase() } diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt index f48fbdb6f40..f0b097fe50b 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt @@ -840,6 +840,67 @@ object Aggregates : TemplateGroupBase() { } } + val f_reduceOrNull = fn("reduceOrNull(operation: (acc: T, T) -> T)") { + include(ArraysOfPrimitives, ArraysOfUnsigned, CharSequences) + } builder { + since("1.3") + annotation("@ExperimentalStdlibApi") + inline() + specialFor(ArraysOfUnsigned) { inlineOnly() } + + doc { "Accumulates value starting with the first ${f.element} and applying [operation] from left to right to current accumulator value and each ${f.element}. Returns null if the ${f.collection} is empty." } + returns("T?") + body { + """ + if (isEmpty()) + return null + + var accumulator = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator + """ + } + } + + val f_reduceOrNullSuper = fn("reduceOrNull(operation: (acc: S, T) -> S)") { + include(ArraysOfObjects, Iterables, Sequences) + } builder { + since("1.3") + annotation("@ExperimentalStdlibApi") + inline() + + doc { "Accumulates value starting with the first ${f.element} and applying [operation] from left to right to current accumulator value and each ${f.element}. Returns null if the ${f.collection} is empty." } + typeParam("S") + typeParam("T : S") + returns("S?") + body { + """ + val iterator = this.iterator() + if (!iterator.hasNext()) return null + + var accumulator: S = iterator.next() + while (iterator.hasNext()) { + accumulator = operation(accumulator, iterator.next()) + } + return accumulator + """ + } + body(ArraysOfObjects) { + """ + if (isEmpty()) + return null + + var accumulator: S = this[0] + for (index in 1..lastIndex) { + accumulator = operation(accumulator, this[index]) + } + return accumulator + """ + } + } + val f_reduceRight = fn("reduceRight(operation: (T, acc: T) -> T)") { include(CharSequences, ArraysOfPrimitives, ArraysOfUnsigned) } builder {