Swift 高阶函数

本文深入探讨了Swift中的高阶函数,包括map、flatMap、compactMap、filter和reduce。通过实例解析了这些函数的基本使用和源码分析,揭示了它们如何简化代码、提高效率。文章还特别讨论了Optional中的map和flatMap,以及它们与数组中对应函数的区别。最后,通过有趣的拓展应用展示了reduce的更多可能性。
摘要由CSDN通过智能技术生成

Swift 高阶函数

高阶函数的定义:

Wikipedia 中,是这么定义高阶函数(higher-order function)的,如果一个函数:

  • 接收一个或多个函数当作参数
  • 把一个函数当作返回值

至少满足以上条件中的一个的函数,那么这个函数就被称作高阶函数。

使用高阶函数进行函数式编程的优势

  • 简化代码
  • 使逻辑更加清晰
  • 当数据比较大的时候,高阶函数会比传统实现更快,因为它可以并行执行(如运行在多核上)

高阶函数在Swift语言中有大量的使用场景,本篇分析 Swift 提供的如下几个高阶函数:mapflatMapcompactMapfilterreduce


一、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: ContiguousArraySwift提供的更高性能的数组,几乎与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 。

仔细观察,flatMapmap是有一些区别的:

A. transform的差别

  • maptransform接收的参数是数组元素然后输出的是闭包执行后的类型T,最终执行的结果的是[T]
  • flatMaptransform接收的参数是数组的元素,但输出的一个Sequence类型,最终执行的结果并不是Sequence的数组,而是Sequence内部元素另外组成的数组,即:[Sequence.Element]

B. 第三个步骤的差别

  • map使用append方法放入result中,所以transform之后的结果是什么类型,就将什么类型放入result中;
  • flatMap使用append-contentsOf方法放入result中,而appendContentsOf方法就是把Sequence中的元素一一取出来,然后再放入result中,这也就是flatMap能降维的原因。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值