《Advanced Swift》笔记1:数组变形(一) —— map 和 flatMap | LiJun's Blog
map和flatMap是Swift数组中的两个高阶函数,他们能很方便的对数组内的所有元素进行操作,然后返回一个新的数组。本文将探索这两个函数的实际使用以及其内部实现。
map
在objective-c中,如果我们要对一个数组里的每个元素进行操作,我们都是通过写for循环来遍历数组,然后在再对元素进行操作。比如我们要对一个数组中的所以元素做平方,并返回新的数组,我们可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | NSArray *array = @[@(1), @(3), @(5), @(6)]; NSMutableArray *sauared = [NSMutableArray array]; for (NSNumber *number in array) { NSInteger num = number.integerValue; [sauared addObject:@(num * num)]; } NSLog(@"%@", sauared); // 最后的输出为: 2016-07-31 15:20:52.497 Array[9945:5153603] ( 1, 9, 25, 36 ) |
在Swift中,为数组提供了一个map方法来做这件事,因此上面的代码在Swift中可以这样写:
1 2 3 | let array = [1, 2, 3] let sauared = x.map{$0 * $0} // sauared = [1, 4, 9] |
使用map方法的优势很明显:
- 代码会变得非常简洁,一句代码就能搞定,代码量变少了,也意味着出错的概率变小了。
- 语义更加明确,也就是代码可读性更好。通过
map这个方法名,我们一眼就知道这个方法要做什么,而如果是通过for循环,我们必须看完整个上下文的代码的才知道在做什么。 - 在Swift中使用
map还有一个优点就是能使用let把新的数组定义为不可变量。如果是用for循环,因为要在循环中逐个添加新的数组元素,因此新的数组必须要用var来定义。这样也提高了代码的安全性。
map 的内部实现
map函数的内部实现原理也非常简单,就是把for循环中的代码用一个泛型函数封装起来了。源码地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public func map<T>( _ transform: @noescape (Iterator.Element) throws -> T ) rethrows -> [T] { // TODO: swift-3-indexing-model - review the following let count: Int = numericCast(self.count) if count == 0 { return [] } var result = ContiguousArray<T>() result.reserveCapacity(count) var i = self.startIndex for _ in 0..<count { result.append(try transform(self[i])) formIndex(after: &i) } _expectEnd(i, self) return Array(result) } |
我们也可以这样简单地实现:
1 2 3 4 5 6 7 8 9 10 | extension Array { func my_map<U>(transform: Element -> U) -> [U] { var result: [U] = [] result.reserveCapacity(self.count) for x in self { result.append(transform(x)) } return result } } |
flatMap
flatMap和map很像,都是对数组中每个元素进行操作转换,但flatMap会做两件额外的事,一是将结果数组展平,如果结果数组中的元素也是一个数组,就会将这个数组展平;二是会把数组中的nil过滤,返回一个不包含nil的数组。这两个额外的操作,其实是分别对应flatMap的两个方法, 因此在一个flatMap方法中这两个额外操作是不能同时执行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | extension SequenceType { /// Returns an `Array` containing the concatenated results of mapping /// `transform` over `self`. /// /// s.flatMap(transform) /// /// is equivalent to /// /// Array(s.map(transform).flatten()) /// /// - Complexity: O(*M* + *N*), where *M* is the length of `self` /// and *N* is the length of the result. public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element] } extension SequenceType { /// Returns an `Array` containing the non-nil results of mapping /// `transform` over `self`. /// /// - Complexity: O(*M* + *N*), where *M* is the length of `self` /// and *N* is the length of the result. public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T] } |
从文档中我们可以看到第一个flatMap的调用者中的元素其实是一个数组(SequenceType),返回的是一个完全展开的一次数组,通过实际代码,我们可以看出其中的区别:
1 2 3 4 5 | let array = [[1, 2, 3, 4], [5, 6, 7]] let mapArray = array.map{$0.map{$0 * $0}} // mapArray = [[1,4,9,16],[25,36,49]] let flatMapArray = array.flatMap{$0.map{$0 * $0}} // flatMapArray = [1,4,9,16,25,36,49] |
文档的解释是第一个flatMap函数相当于Array(s.map(transform).flatten())
第二个flatMap方法则会返回一个不带nil的数组,示例如下:
1 2 3 4 5 | let nilArray: [Any?] = [1, 2, nil, 3] let mapArray = nilArray.map{$0} // mapArray = [1,2,nil,3] let flatMapArray = nilArray.flatMap{$0} // flatMapArray = [1,2,3] |
小思考:下面的代码各会返回什么结果
12345678910 > let sArray: [[Int?]] = [[1, 2, 3, nil, 4]]> sArray.flatMap{$0}>> let array = [[1, 2, 3], 4, [5, 6]]> array.flatMap{$0}>> let array: [Any?] = [[1, 2, 3], 4, [5, 6], nil]> array.flatMap{$0}>>
flatMap 的内部实现
第一个flatMap的源码地址
1 2 3 4 5 6 7 8 9 10 | public func flatMap<SegmentOfResult : Sequence>( _ transform: @noescape (${GElement}) throws -> SegmentOfResult ) rethrows -> [SegmentOfResult.${GElement}] { var result: [SegmentOfResult.${GElement}] = [] for element in self { result.append(contentsOf: try transform(element)) } return result } |
从源码中我们可以发现,flatMap方法之所以能将数组展开,关键是在添加结果是使用的是appendContentsOf方法,这个方法能把数组中的元素取出,放入一个新数组中。
第二个flatMap的源码地址
1 2 3 4 5 6 7 8 9 10 11 12 | public func flatMap<ElementOfResult>( _ transform: @noescape (${GElement}) throws -> ElementOfResult? ) rethrows -> [ElementOfResult] { var result: [ElementOfResult] = [] for element in self { if let newElement = try transform(element) { result.append(newElement) } } return result } |
第二个flatMap方法内部则使用了一个if let判断来过滤nil元素。