阅读 242

Swift:Array的map\compactMap\flatMap函数

前言

map:compactMap:flatMap.jpg

今天简单说一下,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 }复制代码

是不是有这种感觉:

image.png

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功能时,它过期了

比如你这么写函数,会得到一个告警:

image.png

看源代码,上面也说得非常的清晰,过期了:

@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的真正用法

05fa2072ceec4a3d88c8b5f14d1c1c7a.jpeg

当数组是一个二维数组的时候,使用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


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐