Swift:Array的map\compactMap\flatMap函数
前言
今天简单说一下,Swift中Array的常用一组函数——map
系函数。
大家有时可能会对map\compactMap\flatMap
这三个函数的使用场景傻傻分不清,希望通过这篇文章,能够正确的理解与灵活运用。
进而对集合类型的这类函数都可以举一反三。
map
map函数,我个人感觉是这这三个中最基础和最简单的函数。
它的作用就是对数组的每个元素做转换,形成一个新数组。
@inlinable public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]复制代码
大家注意的是,它的返回是一个泛型,而通过Swift类型的自动推断,有的时候会不知道返回的到底是什么类型,需要仔细阅读map里面这个转换。
例子:
let cast = ["Vivien", "Marlon", "Kim", "Karl"] let lowercaseNames = cast.map { $0.lowercased() } print(lowercaseNames) ["vivien", "marlon", "kim", "karl"] ////////////////////////////////////////////// let letterCounts = cast.map { $0.count } print(letterCounts) [6, 6, 3, 4]复制代码
其实map函数有很多简化写法,非常容易迷惑人,来花式秀一下:
[1, 2, 3].map({ (i: Int) -> Int in return i * 2 }) [1, 2, 3].map({ i in return i * 2 } ) [1, 2, 3].map({ i in i * 2 }) [1, 2, 3].map({ $0 * 2 }) [1, 2, 3].map() { $0 * 2 } [1, 2, 3].map{ $0 * 2 }复制代码
是不是有这种感觉:
compactMap
compactMap可以认为是map进阶版本,在转换过程中过滤掉数组中的nil元素,让我们继续看个例子:
let possibleNumbers = ["1", "2", "three", "///4///", "5"] let mapped: [Int?] = possibleNumbers.map { str in Int(str) } [1, 2, nil, nil, 5] ////////////////////////////////////////////// let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) } [1, 2, 5]复制代码
flatMap
相比较map和compactMap,flatMap具有一定的迷惑性。
作为去nil功能时,它过期了
比如你这么写函数,会得到一个告警:
看源代码,上面也说得非常的清晰,过期了:
@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]复制代码
这个时候编译器建议你使用compactMap函数。
作为二维数组降成一维数组的API调用,才是flatMap的真正用法
当数组是一个二维数组的时候,使用flatMap函数它不会报告警说方法过期,而是会对数组进行展平操作——二向箔攻击:
let someArray = [[1, 2, 3], [4, 5, 6]].flatMap { $0 } print(someArray) [1, 2, 3, 4, 5, 6]复制代码
实际调用的是这个API:
@inlinable public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence复制代码
是不是感觉有点蒙圈?一会说过期,一会又可以正常使用。
主要还是要看flatMap函数具体实现了什么功能。
大家想想,如果是下面这个数组,能够顺利变成一维数组么:
let anotherArray = [[1, 2, 3], [4, 5, [6, 7]]].flatMap { $0 } print(anotherArray)复制代码
结果是[1, 2, 3, 4, 5, [6, 7]]
,没有完全展平。
flatMap它只能展平二维数组。如果一个二维数组中还有数组,它就无法展平了。
map函数的内部实现
手撕版:
其实map函数的内部实现并不复杂,其实主要就是用了for循环。
extension Array { func map<T>(_ transform: (Element) -> T) -> [T] { var newArray:[T] = [] for element in self { let newElement = transform(element) newArray.append(newElement) } return newArray } }复制代码
官方版:
官方版说实话比我想的要复杂,而且是放在Collection协议的extension中,并且官方版添加了对错误的处理。
感兴趣的看看源码:Swift Collection
extension Collection { @inlinable 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) } }复制代码
从Array到Collection
既然官方的map函数写在Collection中的extension里面,那么你可以想想其他集合类型的map系函数的使用是不是也大同小异呢?
之前我写的这篇文章,被很多掘友认同,我感到很惊讶:Swift:为String、Array、Dictionary添加isNotEmpty属性。
我想了半天,得到了一个结论,是不是因为大家一般写Swift的extension的时候,一般都是在原生上的class
或者struct
去做操作,而很少在protocol
这种没有具体实现的层面进行处理导致的呢?
参考文档
Swift:为String、Array、Dictionary添加isNotEmpty属性
总结
本篇针对Swift的Array讲解了map\compactMap\flatMap
函数,大家找到合适的使用场景,就可以完成相对比较复杂的功能。
需要注意的是flatMap函数,它作为去数组中nil元素的功能已经过期,请使用compactMap,它作为展平函数,只能展平二维数组。
请活用高级函数去代替forin
循环。
通过查看源码,从Array抽象到Collection协议,我们可以感受到Swift设计的思路。
作者:season_zhu
链接:https://juejin.cn/post/7022840120881446919