在 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:) 办法能够供给更好的功能。在实际运用中,应该依据具体情况挑选适宜的办法。