如何在空的 Kotlin 数组上调用 reduce 函数?

46

对于空数组的简单缩减操作会抛出异常:

Exception in thread "main" java.lang.UnsupportedOperationException: Empty iterable can't be reduced.

在链接时也会出现同样的异常:

val a = intArrayOf()

val b = a.reduce({ memo, next -> memo + next }) // -> throws an exception

val a1 = intArrayOf(1, 2, 3)

val b1 = a.filter({ a -> a < 0 }).reduce({ a, b -> a + b }) // -> throws an exception

这是reduce函数预期的操作还是一个错误?

有没有什么解决方法?


4
如果你只是用reduce来求和,那么你可以在数字列表上使用内置的.sum()函数;如果你正在对某些特殊的东西进行求和,那么你也可以编写扩展函数来帮助解决问题。 - Richard Green
4个回答

84

异常是正确的,reduce 在空迭代器或数组上无法工作。你可能正在寻找的是fold,它接受一个起始值和一个操作,该操作依次应用于可迭代对象的每个元素。reduce 将第一个元素作为起始值,因此它不需要传递额外的值作为参数,但需要保证集合不为空。

fold 的使用示例:

println(intArrayOf().fold(0) { a, b -> a + b })  // prints "0"

谢谢,折叠似乎是解决方案 :) 因此,我可以理解链接的过滤和缩减是有风险的,因为过滤器可能会返回一个空列表,而缩减将始终被调用,因此会抛出异常。 - dippe
1
为什么不将reduce的定义与ECMAScript 6中的定义类似?让reduce接受两个参数,一个是函数,另一个是初始值。如果没有定义初始值(即null),则reduce会将数组中的第一个值作为初始值。 - Stacky
1
他们称之为“reduce”吗?如果是这样,那些寻找“fold”的人可能不会意识到他们需要在想要“fold”时使用“reduce”。如果他们也提供了“fold”,那么他们就创造了两种完全相同的方法。无论哪种方式,这似乎都会为程序员创建额外的障碍。我认为这对于语言来说并不合适;然而,如果我们发现需要/想要这种简写,我们仍然可以通过创建自己的函数来实现相同的效果。 - methodsignature

18

我只是想为那些无法使用 fold(...) 的情况添加更一般性的方法。因为,为了使用 fold,你需要能够表达一些初始值。

someIterable
    .filter { TODO("possibly filter-out everything") }
    .takeIf { it.isNotEmpty() }
    ?.reduce { acc, element -> TODO("merge operation") }
    ?: TODO("value or exception for empty")

使用这种方法,在集合为空的情况下,reduce 不会执行,因为 takeIf 会将其转换为 null。最后,我们可以使用 Elvis 运算符来表示在这种情况下返回某个值(或抛出异常)。

你的示例:

intArrayOf(1, 2, 3)
    .filter { a -> a < 0 }
    .takeIf { it.isNotEmpty() }
    ?.reduce { a, b -> a + b }
    ?: 0

编辑:

自从Kotlin 1.4版以来,有一个标准库函数reduceOrNull,比.takeIf { it.isNotEmpty() }方法更适用。

通用情况:

someIterable
    .filter { TODO("possibly filter-out everything") }
    .reduceOrNull { acc, element -> TODO("merge operation") }
    ?: TODO("value or exception for empty")

用于解决您的问题:

intArrayOf(1, 2, 3)
    .filter { a -> a < 0 }
    .reduceOrNull { a, b -> a + b }
    ?: 0

如何在使用reduce时在字符串上添加断点? - Atif AbbAsi
你可以在lambda主体上简单地打断点,这将为每个reduce子步骤打断,使您能够检查ab - Antonio Tomac
谢谢回复。我所做的是使用了 take(numberOfItem),然后它就可以工作了。 - Atif AbbAsi

3
public inline fun <S, T : S> List<T>.reduceRightDefault(defaultIfEmpty: S, operation: (T, acc: S) -> S): S {
    return if (isEmpty()) defaultIfEmpty
    else reduceRight(operation)
}

用法:

val result = listOf<Boolean>().reduceRightDefault(false) { first, second -> first && second}

println("result $result")//result false

0
你可以使用foldRight:
println(listOf("1", "2", "3")
    .filter { "not found" == it }
    .foldRight("") { a, b -> a + b }) // prints: ""

println(listOf("1", "2", "3")
    .filter { "not found" != it }
    .foldRight("") { a, b -> a + b }) // prints: "123"

或在你的情况下:

val a = intArrayOf()
val b = a.foldRight(0) { memo, next -> memo + next } // b == 0
val a1 = intArrayOf(1, 2, 3)
val b1 = a.filter { a -> a < 0 }.foldRight(0) { a, b -> a + b } // b1 == 0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接