2024年5月

Swift高阶函数

高阶函数是指可以接收函数作为参数或者返回函数作为结果的函数。Swift 标准库提供了多个强大的高阶函数,主要用于集合类型的操作。以下是 Swift 中常用的高阶函数及其使用场景:

1. map(_:)

功能:对集合中的每个元素进行转换,返回一个新的数组。

使用场景:当你需要对数组中的每个元素进行相同操作并得到新数组时。

let numbers = [1, 2, 3, 4]
let squared = numbers.map { $0 * $0 }  // [1, 4, 9, 16]

2. compactMap(_:)

功能:类似于 map,但会自动过滤掉 nil 值。

使用场景:转换可能产生 nil 的情况,并希望自动去除 nil 值。

let strings = ["1", "2", "three", "4"]
let numbers = strings.compactMap { Int($0) }  // [1, 2, 4]

3. flatMap(_:)

功能:将二维数组"压平"为一维数组,或处理嵌套可选值。

使用场景

  • 处理嵌套数组
  • 处理嵌套可选值(Swift 4.1+ 中对于可选值推荐使用 compactMap)
let nestedArray = [[1, 2, 3], [4, 5, 6]]
let flattened = nestedArray.flatMap { $0 }  // [1, 2, 3, 4, 5, 6]

4. filter(_:)

功能:根据条件过滤集合中的元素。

使用场景:需要从集合中筛选符合条件的元素时。

let numbers = [1, 2, 3, 4, 5, 6]
let evens = numbers.filter { $0 % 2 == 0 }  // [2, 4, 6]

5. reduce(_:_:)

功能:将集合中的所有元素组合成一个值。

使用场景:需要计算总和、拼接字符串或任何需要将集合"缩减"为单个值的操作。

let numbers = [1, 2, 3, 4]
let sum = numbers.reduce(0) { $0 + $1 }  // 10
let product = numbers.reduce(1, *)  // 24

6. sorted(by:)

功能:根据条件对集合进行排序。

使用场景:需要自定义排序规则时。

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let sortedNames = names.sorted(by: { $0 < $1 })  // 升序排列

7. forEach(_:)

功能:对集合中的每个元素执行操作。

使用场景:需要对每个元素执行操作但不需要返回值时(与 map 的区别)。

["a", "b", "c"].forEach { print($0) }

8. contains(where:)

功能:检查集合中是否包含满足条件的元素。

使用场景:需要检查集合中是否存在符合条件的元素时。

let numbers = [1, 2, 3, 4, 5]
let hasEven = numbers.contains(where: { $0 % 2 == 0 })  // true

9. first(where:)/last(where:)

功能:返回集合中第一个/最后一个满足条件的元素。

使用场景:需要查找集合中第一个或最后一个符合条件的元素时。

let numbers = [10, 20, 30, 40]
let firstMultipleOf15 = numbers.first(where: { $0 % 15 == 0 })  // 30

10. prefix(while:)/drop(while:)

功能

  • prefix(while:):从开头开始获取满足条件的元素,直到条件不满足
  • drop(while:):跳过开头满足条件的元素,返回剩余部分

使用场景:需要基于条件获取或跳过集合开头部分时。

let scores = [85, 90, 78, 92, 88, 76]
let goodScores = scores.prefix(while: { $0 >= 85 })  // [85, 90]
let afterFirstBad = scores.drop(while: { $0 >= 85 })  // [78, 92, 88, 76]

11. zip(_:_:)

功能:将两个序列组合成一个元组序列。

使用场景:需要同时遍历两个集合时。

let names = ["Alice", "Bob", "Charlie"]
let scores = [85, 92, 78]
for (name, score) in zip(names, scores) {
    print("\(name): \(score)")
}

使用建议

  1. 链式调用:可以组合多个高阶函数

    let result = (1...10)
        .filter { $0 % 2 == 0 }
        .map { $0 * $0 }
        .reduce(0, +)
  2. 性能考虑:每个高阶函数都会创建一个新集合,对于大数据集可能需要考虑性能
  3. 可读性:当逻辑复杂时,有时传统的 for 循环可能更易读

这些高阶函数使 Swift 代码更加简洁、表达力更强,是函数式编程风格的重要组成部分。

补充:flatMap 与 compactMap 的区别

compactMap 不会自动将二维数组降为一维数组,这是它与 flatMap 的一个重要区别。

主要区别

  1. compactMap:

    • 主要用途:在映射过程中过滤掉 nil
    • 不会自动"压平"二维数组
    • 返回的数组维度与输入相同
  2. flatMap (在 Swift 4.1 及以后版本):

    • 主要用途:将二维数组降为一维数组
    • 不处理 nil 值过滤(对于可选值过滤,改用 compactMap

示例对比

1. compactMap 行为

let nestedArray = [[1, nil, 3], [nil, 5, 6]]
let result = nestedArray.compactMap { $0 }
print(result) 
// 输出: [[Optional(1), nil, Optional(3)], [nil, Optional(5), Optional(6)]]
// 仍然是二维数组,只是对最外层数组进行了 nil 过滤(这里没有 nil 所以没变化)

2. 正确使用 compactMap 过滤 nil

let arrayWithOptionals: [Int?] = [1, nil, 3, nil, 5]
let nonNilValues = arrayWithOptionals.compactMap { $0 }
print(nonNilValues) // 输出: [1, 3, 5]

3. flatMap 降维示例

let nestedArray = [[1, 2, 3], [4, 5, 6]]
let flattened = nestedArray.flatMap { $0 }
print(flattened) // 输出: [1, 2, 3, 4, 5, 6]

历史变化

  • Swift 4.1 之前flatMap 有三种功能:

    1. 二维数组降维
    2. 过滤 nil (类似现在的 compactMap)
    3. 组合操作
  • Swift 4.1 及以后

    • flatMap 只保留降维功能
    • 过滤 nil 的功能迁移到 compactMap

如何实现带 nil 过滤的降维

如果需要同时处理二维数组降维和过滤 nil,可以组合使用:

let nestedWithOptionals = [[1, nil, 3], [nil, 5, 6]]
let result = nestedWithOptionals.flatMap { $0 }.compactMap { $0 }
print(result) // 输出: [1, 3, 5, 6]