注册
iOS

Swift 中怎样更快地 reduce

在 Swift 中,对于集合类型,Swift 标准库提供了若干方便的方法,可以对数据进行处理,其中一个比较常见的就是 reduce。reduce 这个单词,通过查阅字典,可以发现其有“简化、归纳”的意思,也就是说,可以用 reduce 把一组数据归纳为一个数据,当然这个一个数据也可以是一个数组或任何类型。


比较常见的 reduce 使用案例,例如:


求和:

let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0, +)
print(sum) // 输出 15

字符串拼接:

let words = ["hello", "world", "how", "are", "you"]
let sentence = words.reduce("", { $0 + " " + $1 })
print(sentence) // 输出 " hello world how are you"

两个 reduce API


观察 reduce 方法的声明,会发现有两个不同的 API,一个是 reduce 一个是 reduce(into:),他们的功能是一样的,但是却略有不同。


reduce 方法的函数签名如下:

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

该方法接收一个初始值和一个闭包作为参数,该闭包将当前的结果值和集合中的下一个元素作为输入,并返回一个新的结果值。reduce 方法依次迭代集合中的每个元素,并根据闭包的返回值更新结果值,最终返回最终结果值。


还是回到最简单的求和上来,下面的代码使用 reduce 方法计算一个数组中所有元素的总和:

let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0, { $0 + $1 })
print(sum) // 输出 15

reduce(into:) 方法的函数签名如下:

func reduce<Result>( into initialResult: __owned Result, _ updateAccumulatingResult: (inout Result, Element) throws -> Void ) rethrows -> Result

该方法接收一个初始值和一个闭包作为参数,该闭包将当前的结果值和集合中的下一个元素作为输入,并使用 inout 参数将更新后的结果值传递回去。reduce(into:) 方法依次迭代集合中的每个元素,并根据闭包的返回值更新结果值,最终返回最终结果值。


下面的代码使用 reduce(into:) 方法计算一个数组中所有元素的总和:

let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(into: 0, { result, element in
result += element
})
print(sum) // 输出 15

可以看到,reduce(into:) 方法中闭包的参数使用了 inout 关键字,使得闭包内部可以直接修改结果值。这样可以避免不必要的内存分配和拷贝,因此在处理大量数据时,使用 reduce(into:) 方法可以提高性能。


观察源码


我们再通过观察源码证实这一结论


reduce 方法的源码实现如下:

public func reduce<Result>(
_ initialResult: Result,
_ nextPartialResult: (Result, Element) throws -> Result
) rethrows -> Result {
var accumulator = initialResult
for element in self {
accumulator = try nextPartialResult(accumulator, element)
}
return accumulator
}

可以发现这里有两处拷贝,一处是在 accumulator 传参给 nextPartialResult 时,一处是在把 nextPartialResult 的结果赋值给 accumulator 变量时,由于这里的 accumulator 的类型是一个值类型,每次赋值都会触发 Copy-on-Write 中的真正的拷贝。并且这两处拷贝都是在循环体中,如果循环的次数非常多,是会大大拖慢性能的。


再看 reduce(into:) 方法的源码:

func reduce<Result>( 
into initialResult: __owned Result,
_ updateAccumulatingResult: (inout Result, Element) throws -> Void
) rethrows -> Result {
var result = initialResult
for element in self {
try updateAccumulatingResult(&result, element)
}
return result
}

在方法的实现中,我们首先将 initialResult 复制到一个可变变量 result 中。然后,我们对序列中的每个元素调用 updateAccumulatingResult 闭包,使用 & 语法将 result 作为 inout 参数传递给该闭包。因此这里每次循环都是在原地修改 result 的值,并没有发生拷贝操作。


总结


因此,reduce 方法和 reduce(into:) 方法都可以用来将集合中的元素组合成单个值,但是对于会触发 Copy-on-Write 的类型来说, reduce(into:) 方法可以提供更好的性能。在实际使用中,应该根据具体情况选择合适的方法。


作者:SwiftFun
链接:https://juejin.cn/post/7219712310586982457
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册