Swift 高阶函数
高阶函数的定义:
在 Wikipedia
中,是这么定义高阶函数(higher-order function)的,如果一个函数:
- 接收一个或多个函数当作参数
- 把一个函数当作返回值
至少满足以上条件中的一个的函数,那么这个函数就被称作高阶函数。
使用高阶函数进行函数式编程的优势:
- 简化代码
- 使逻辑更加清晰
- 当数据比较大的时候,高阶函数会比传统实现更快,因为它可以并行执行(如运行在多核上)
高阶函数在Swift
语言中有大量的使用场景,本篇分析 Swift
提供的如下几个高阶函数:map
、flatMap
、compactMap
、filter
、reduce
。
一、map
map
方法获取一个闭包表达式作为其唯一参数。
数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值。
简单说就是数组中每个元素通过**某种规则(闭包实现)**进行转换,最后返回一个新的数组。
1. 基本使用
a. 需求:将Int
类型数组中的元素乘以2,然后转换为String
类型的数组
let ints = [1, 2, 3, 4]
let strs = ints.map { "\($0 * 2)" }
// 打印结果:["2", "4", "6", "8"]
print(strs)
b. 需求:生成一个新的Int数组,元素是多少元素就重复多少个
let nums = [1, 2, 3, 4]
let mapNums = nums.map { Array(repeating: $0, count: $0) }
// 打印结果:[[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
print(mapNums)
最终返回的是一个二维数组。
c. 需求:将String
类型的数组转换为Int
类型的数组
let someAry = ["12", "ad", "33", "cc", "22"]
// var nilAry: [Int?]
let nilAry = someAry.map { Int($0) }
// 打印结果:[Optional(12), nil, Optional(33), nil, Optional(22)]
print(nilAry)
最终返回的是[Int?]
,一个可选类型的Int
数组,且元素中存在nil
。
2. 源码分析
由于Swfit是开源的,所以我们可以通过源码来分析map
具体做了些什么。
源码:https://github.com/apple/swift/blob/master/stdlib/public/core/Collection.swift
public func map<T>(
_ transform: (Element) throws -> T
) rethrows -> [T] {
// TODO: swift-3-indexing-model - review the following
let n = self.count
if n == 0 {
return []
}
var result = ContiguousArray<T>()
result.reserveCapacity(n)
var i = self.startIndex
for _ in 0..<n {
result.append(try transform(self[i]))
formIndex(after: &i)
}
_expectEnd(of: self, is: i)
return Array(result)
}
对于这个代码,我们可以看出,它做了以下几件事情:
1. 构造一个名为 result 且与原数组的 capacity 一致的新数组,用于存放新的结果;
2. 遍历自己的元素,对于每个元素,调用闭包的转换函数 transform ,进行转换;
3. 将转换的结果使用 append 方法放入 result 中;
4. 遍历完成后,返回 result 。
tips: ContiguousArray
是Swift
提供的更高性能的数组,几乎与Array没什么区别,如果不涉及Objective-C
的混编或需要转NSArray
,完全可以使用ContiguousArray
取代Array
来使用,可以有更高的性能。
至于与Array
的区别,这里就不拓展了,有兴趣的小伙伴Google
一下。
二、flatMap
// flatMap 定义
public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
// Swift 4.1 以前的定义,4.1之后改名为 compactMap,compactMap时会详细说明
@available(swift, deprecated: 4.1, renamed: "compactMap(_:)", message: "Please use compactMap(_:) for the case where closure returns an optional value")
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
flatMap
的实现与map
非常类似,也是数组中每个元素通过**某种规则(闭包实现)**进行转换,最后返回一个新的数组。
不过flatMap
能把数组中存有数组的数组(二维数组、N维数组)一同打开变成一个新的数组,称为降维,通俗一点就是把多维数组都会拍扁为一维数组,通过后面的例子一看就明白了。
flatMap
另一个解包的功能在 4.1 版本之后更名为compactMap
,所以在compactMap
再做说明。
1. 基本使用
a. 需求:将Int
类型数组中的元素乘以2,然后转换为String
类型的数组
let ints = [1, 2, 3, 4]
let strs = ints.flatMap { "\($0 * 2)" }
// flatMap打印结果:["2", "4", "6", "8"]
print(strs)
该例子使用的与map
一样,结果也是一样的,普通情况下,两者的效果一致。
b. 需求:生成一个新的Int数组,元素是多少元素就重复多少个
let nums = [1, 2, 3, 4]
let mapNums = nums.flatMap { Array(repeating: $0, count: $0) }
// flatMap打印结果:[1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
print(mapNums)
可以看到,flatMap
把数组中的数组都打开了,最终返回的是一个一维数组。而map
返回的是一个二维数组,没有降维。接下来通过源码,来分析两者的差别。
2. 源码分析
源码:https://github.com/apple/swift/blob/master/stdlib/public/core/SequenceAlgorithms.swift
public func flatMap<SegmentOfResult: Sequence>(
_ transform: (Element) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.Element] {
var result: [SegmentOfResult.Element] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}
我们可以看出,它做了以下几件事情:
1. 构造一个名为 result 的新数组,用于存放新的结果;
2. 遍历自己的元素,对于每个元素,调用闭包的转换函数 transform ,进行转换;
3. 将转换的结果使用 append-contentsOf 方法放入 result 中;
4. 遍历完成后,返回 result 。
仔细观察,flatMap
与map
是有一些区别的:
A. transform
的差别
map
的transform
接收的参数是数组元素然后输出的是闭包执行后的类型T
,最终执行的结果的是[T]
flatMap
的transform
接收的参数是数组的元素,但输出的一个Sequence
类型,最终执行的结果并不是Sequence
的数组,而是Sequence
内部元素另外组成的数组,即:[Sequence.Element]
B. 第三个步骤的差别
map
使用append
方法放入result
中,所以transform
之后的结果是什么类型,就将什么类型放入result
中;flatMap
使用append-contentsOf
方法放入result
中,而appendContentsOf
方法就是把Sequence
中的元素一一取出来,然后再放入result
中,这也就是flatMap
能降维的原因。