0%

Swift中的reduce方法

在常用的高阶函数中,相比于 mapflatMapfilter 这些,reduce 理解起来更晦涩一些,不如前面几个功能明确。

reduce 一词,在英文中的解释为“减少、降低等”,在函数式编程中,我们可以把这个单词理解为“合并、归纳”。也就是说,reduce 的作用是合并结果,把多项变成一项。至于怎么合并,就看传入的转换方法是什么。

部分编程语言也会使用 foldcompress 等表示 reduce ,参考 https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29 。但是主流编程语言中,还是使用 reduce 一词的比较多。

Swift中的 reduce

Swift提供了两个不同的 reduce 方法。

  • reduce(_:_:)
1
2
/// Returns the result of combining the elements of the sequence using the given closure.
@inlinable public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
  • reduce(into:_:)
1
2
/// Returns the result of combining the elements of the sequence using the given closure.
@inlinable public func reduce<Result>(into initialResult: Result, _ updateAccumulatingResult: (inout Result, Element) throws -> ()) rethrows -> Result

两个方法的作用完全一样,都是 Returns the result of combining the elements of the sequence using the given closure ,翻译过来就是 使用传入的closure合并序列并返回 。在使用上两个方法有什么区别呢?

TLDR: 具体作用没区别。只是当返回结果为ArrayDictionaryCoy On Write 类型的数据时,优先使用reduce(into:_:) 以提升性能。

reduce(_:_:)

1
2
3
4
5
6
7
8
9
10
11
12
13
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value.
/// `initialResult` is passed to `nextPartialResult` the first time the
/// closure is executed.
/// - nextPartialResult: A closure that combines an accumulating value and
/// an element of the sequence into a new accumulating value, to be used
/// in the next call of the `nextPartialResult` closure or returned to
/// the caller.
/// - Returns: The final accumulated value. If the sequence has no elements,
/// the result is `initialResult`.
///
/// - Complexity: O(*n*), where *n* is the length of the sequence.
@inlinable public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

以下面的代码为例,介绍reduce(_:_:)的执行流程。

1
2
3
4
5
let numbers = [1, 2, 3, 4]
let numberSum = numbers.reduce(0, { x, y in
x + y
})
// numberSum == 10
  1. 调用 nextPartialResult,传入的参数为 x: *initialResult0),y:*numbers 数组第一个元素(1),返回相加的结果:1
  2. 继续调用 nextPartialResult,传入的参数为 x: 上一步的返回值,ynumbers 数组下一个元素,返回相加的结果。
  3. numbers 数组遍历完毕后,将最后的结果最为返回值返回。

如果 numbers 数组为空,则 nextPartialResult 不会执行,直接返回 initialResult

reduce(into:_:)

1
2
3
4
5
6
7
8
9
/// - Parameters:
/// - initialResult: The value to use as the initial accumulating value.
/// - updateAccumulatingResult: A closure that updates the accumulating
/// value with an element of the sequence.
/// - Returns: The final accumulated value. If the sequence has no elements,
/// the result is `initialResult`.
///
/// - Complexity: O(*n*), where *n* is the length of the sequence.
@inlinable public func reduce<Result>(into initialResult: Result, _ updateAccumulatingResult: (inout Result, Element) throws -> ()) rethrows -> Result

以下面的代码为例,介绍reduce(into:_:)的执行流程。

1
2
3
4
5
let letters = "abracadabra"
let letterCount = letters.reduce(into: [:]) { counts, letter in
counts[letter, default: 0] += 1
}
// letterCount == ["a": 5, "b": 2, "r": 2, "c": 1, "d": 1]
  1. 调用 updateAccumulatingResult,传入的参数为 counts: *initialResult[:]),letter: *letters 数组第一个元素(a),返回结果:[a: 1]
  2. 继续调用 updateAccumulatingResult ,传入的参数为 counts: 上一步的返回值,letterletters 数组下一个元素,返回结果。
  3. letters 数组遍历完毕后,将最后的结果最为返回值返回。

如果 letters 数组为空,则 updateAccumulatingResult 不会执行,直接返回 initialResult

总结

reduce(_:_:)reduce(into:_:) 除了参数不一样外真没啥区别。不过从方法命名上看,我们也能大概猜到 reduce(into:_:) 会把归并结果会更新到 initialResult ,这应该就是Swift官方推荐当返回结果为ArrayDictionaryCoy On Write 类型的数据时,优先使用reduce(into:_:)的原因。

This method(reduce(into:_:)) is preferred over reduce(_:_:) for efficiency when the result is a copy-on-write type, for example an Array or a Dictionary.

使用reduce实现map、filter等

弄明白 reduce 的原理之后,大家可以发现这是一个很灵活的方法。基本上能用 mapfilter 实现的功能都可以用 reduce 代替。

使用reduce实现map

1
2
3
4
5
6
7
extension Array {
public func mapUsingReduce<T>(_ transform: (Element) throws -> T) rethrows -> [T] {
return try self.reduce(into: []) { (result, element) in
result.append(try transform(element))
}
}
}

使用reduce实现filter

1
2
3
4
5
6
7
extension Array {
public func mapUsingReduce<T>(_ transform: (Element) throws -> T) rethrows -> [T] {
return try self.reduce(into: []) { (result, element) in
result.append(try transform(element))
}
}
}

参考