Swift: Swift 高阶函数:map、filter、reduce 与函数式链式调用
高阶函数是指接受函数作为参数或返回函数的函数。Swift 标准库提供了 map、filter、reduce 等强大的高阶函数,让你用声明式风格处理集合数据。
1. 你将学到
- 使用
map对集合中的每个元素进行变换 - 使用
filter筛选满足条件的元素 - 使用
reduce将集合归约为单个值 - 使用
compactMap和flatMap处理可选值和嵌套集合 - 链式调用多个高阶函数实现复杂数据转换
2. 一个数据分析师的真实故事
(1) 痛点:用 for 循环处理数组代码臃肿
Bob 需要从用户数组中提取活跃用户并按年龄排序,同时格式化输出字符串:
SWIFT
var activeUsers: [String] = []
for user in users {
if user.isActive {
activeUsers.append(user.name)
}
}
activeUsers.sort()
var result: [String] = []
for name in activeUsers {
result.append("User: \(name)")
}
两层 for 循环 + 一个临时数组 + 一个排序。逻辑简单但代码长,且充斥着"如何做"(How)的细节,而不是"做什么"(What)。
(2) 高阶函数的解法
使用函数式链式调用,一行完成所有操作:
SWIFT
let result = users
.filter { $0.isActive }
.map { $0.name }
.sorted()
.map { "User: \($0)" }
代码从 10 行变成 4 行,且可读性更强。
(3) 收益:声明式编程的优势
| 维度 | for 循环 | 高阶函数链式调用 |
|---|---|---|
| 代码长度 | 10-15 行 | 3-5 行 |
| 可读性 | 需阅读循环体理解意图 | 函数名直接表达意图 |
| 数据流向 | 需跟踪中间变量 | 链式调用清晰地展现数据流 |
| 修改影响 | 改逻辑需重写循环 | 增删环节不影响其他步骤 |
| 并发友好 | 需手动管理 | 天生支持并行(如 parallelMap) |
3. map:变换每个元素
map 对数组中的每个元素执行变换,返回相同长度的新数组。
graph LR
A["[1, 2, 3, 4, 5]"] --> B["map { $0 * 2 }"]
B --> C["[2, 4, 6, 8, 10]"]
| 场景 | 输入 | 变换 | 输出 |
|---|---|---|---|
| 数值加倍 | [1, 2, 3] |
{ $0 * 2 } |
[2, 4, 6] |
| 提取属性 | [User, User] |
{ $0.name } |
["Alice", "Bob"] |
| 类型转换 | ["1", "2"] |
{ Int($0) } |
[1, 2](类型变为 [Int?]) |
▶ 示例:map 的基本用法
SWIFT
// ============================================
// map:对每个元素应用变换
// ============================================
let numbers = [1, 2, 3, 4, 5]
// for 循环方式(老式)
var doubled: [Int] = []
for n in numbers { doubled.append(n * 2) }
// map 方式(新式)
let doubledMap = numbers.map { $0 * 2 }
print("doubledMap: \(doubledMap)")
// 提取字符串长度
let words = ["Swift", "Python", "Go"]
let lengths = words.map { $0.count }
print("lengths: \(lengths)")
输出:
TEXT 📖 仅展示doubledMap: [2, 4, 6, 8, 10] lengths: [5, 6, 2]
4. filter:筛选元素
filter 根据条件保留元素,返回新数组。
▶ 示例:filter 筛选操作
SWIFT
// ============================================
// filter:保留满足条件的元素
// ============================================
let scores = [45, 82, 91, 63, 77, 55, 88]
// 筛选及格分数(>= 60)
let passed = scores.filter { $0 >= 60 }
print("及格:\(passed)")
// 筛选优秀分数(>= 85)
let excellent = scores.filter { $0 >= 85 }
print("优秀:\(excellent)")
// 连写:筛选不及格并加倍
let failedDoubled = scores
.filter { $0 < 60 }
.map { $0 * 2 }
print("不及格分数加倍:\(failedDoubled)")
输出:
TEXT 📖 仅展示及格:[82, 91, 63, 77, 88] 优秀:[91, 88] 不及格分数加倍:[90, 110]💡 提示:
filter返回的新数组顺序与原数组一致。
5. reduce 与 compactMap
(1) reduce:归约为单个值
reduce 将数组的所有元素组合成一个值。
| 参数 | 含义 | 示例 |
|---|---|---|
initialResult |
初始值 | 0 用于求和,"" 用于拼接 |
nextPartialResult |
累加闭包 | { $0 + $1 } |
graph LR
A["[1, 2, 3, 4]"] --> B["reduce(0) { $0 + $1 }"]
B --> C["0+1=1, 1+2=3, 3+3=6, 6+4=10"]
C --> D["10"]
▶ 示例:reduce 的多种用法
SWIFT
// ============================================
// reduce:归约操作
// ============================================
let numbers = [1, 2, 3, 4, 5]
// 求和
let sum = numbers.reduce(0) { $0 + $1 }
print("sum: \(sum)")
// 求乘积
let product = numbers.reduce(1) { $0 * $1 }
print("product: \(product)")
// 拼接字符串
let words = ["Swift", "is", "awesome"]
let sentence = words.reduce("") { $0.isEmpty ? $1 : "\($0) \($1)" }
print("sentence: \(sentence)")
// 使用命名参数更清晰
let sumNamed = numbers.reduce(0) { total, next in total + next }
print("sumNamed: \(sumNamed)")
输出:
TEXT 📖 仅展示sum: 15 product: 120 sentence: Swift is awesome sumNamed: 15
(2) compactMap:过滤 nil
compactMap 在 map 的基础上自动过滤掉 nil 值。
SWIFT
// ============================================
// compactMap 与 flatMap
// ============================================
// compactMap:过滤 nil
let strings = ["1", "two", "3", "four", "5"]
let numbers2 = strings.compactMap { Int($0) }
print("numbers: \(numbers2)")
// flatMap:展平嵌套数组
let nested = [[1, 2], [3, 4, 5], [6]]
let flattened = nested.flatMap { $0 }
print("flattened: \(flattened)")
输出:
TEXT 📖 仅展示numbers: [1, 3, 5] flattened: [1, 2, 3, 4, 5, 6]
6. 链式调用实战
高阶函数最强大的用法是链式调用——将多个操作串联成一个流水线。
▶ 示例:复杂数据转换链
SWIFT
// ============================================
// 链式调用:用户数据分析流水线
// ============================================
struct User {
let name: String
let age: Int
let isActive: Bool
let scores: [Int]
}
let users = [
User(name: "Alice", age: 28, isActive: true, scores: [85, 90, 78]),
User(name: "Bob", age: 22, isActive: false, scores: [70, 75]),
User(name: "Charlie", age: 35, isActive: true, scores: [95, 92, 98, 100]),
User(name: "Diana", age: 17, isActive: true, scores: [60, 65]),
User(name: "Eve", age: 30, isActive: false, scores: [88, 82])
]
// 链式流水线:活跃用户 → 成年 → 取平均分 → 按分数排列 → 格式化
let report = users
.filter { $0.isActive }
.filter { $0.age >= 18 }
.map { user -> (name: String, avgScore: Double) in
let avg = Double(user.scores.reduce(0, +)) / Double(user.scores.count)
return (user.name, avg)
}
.sorted { $0.avgScore > $1.avgScore }
.map { "\($0.name): \(String(format: "%.1f", $0.avgScore))" }
print("=== Active Adult User Report ===")
report.forEach { print($0) }
输出:
TEXT 📖 仅展示=== Active Adult User Report === Charlie: 96.2 Alice: 84.3
7. 完整示例:购物车价格计算器
SWIFT
// ============================================
// 完整示例:购物车价格计算器
// 功能:map + filter + reduce + compactMap 综合运用
// ============================================
import Foundation
struct CartItem {
let name: String
let price: Double
let quantity: Int
let isDiscounted: Bool
}
let cart = [
CartItem(name: "Laptop", price: 1299, quantity: 1, isDiscounted: false),
CartItem(name: "Mouse", price: 29.99, quantity: 2, isDiscounted: true),
CartItem(name: "Keyboard", price: 99.99, quantity: 1, isDiscounted: true),
CartItem(name: "Cable", price: 9.99, quantity: 3, isDiscounted: false),
CartItem(name: "Monitor", price: 399, quantity: 0, isDiscounted: false)
]
// 1. 过滤零数量商品
let validItems = cart.filter { $0.quantity > 0 }
// 2. 计算每项总价(打折项减 15%)
let itemTotals = validItems.map { item -> (String, Double) in
let unitPrice = item.isDiscounted ? item.price * 0.85 : item.price
let total = unitPrice * Double(item.quantity)
return (item.name, total)
}
// 3. 计算总价
let grandTotal = itemTotals.reduce(0) { $0 + $1.1 }
// 4. 格式化输出
print("=== Shopping Cart ===")
itemTotals.forEach { print(" \($0): $\(String(format: "%.2f", $1))") }
print(" ----------")
print(" Total: $\(String(format: "%.2f", grandTotal))")
输出:
TEXT 📖 仅展示=== Shopping Cart === Laptop: $1299.00 Mouse: $50.98 Keyboard: $84.99 Cable: $29.97 ---------- Total: $1464.94
❓ 常见问题
Q map 和 forEach 有什么区别?
A
map 返回一个新数组,forEach 只遍历不返回。用 map 是为了变换得到新数据,用 forEach 只是为了执行副作用(如打印)。Q flatMap 和 compactMap 有什么不同?
A Swift 4.1+ 中
flatMap 用于展平嵌套集合(如 [[1,2], [3]] → [1,2,3]),compactMap 用于过滤 nil(如 [1, nil, 3] → [1, 3])。两者各司其职。Q 链式调用会让性能变差吗?
A 每次调用都创建新数组,确实有中间分配开销。对小型到中等规模数据(几千元素以内)影响可忽略。对大数据量可考虑
lazy 延迟求值:array.lazy.filter { ... }.map { ... }。Q reduce 可以处理空数组吗?
A 可以。
reduce 直接返回初始值。例如 [].reduce(0, +) 返回 0。但如果初始值不适合空数组场景(如求最大值),需先检查 isEmpty。Q 高阶函数能取代所有 for 循环吗?
A 大部分场景可以。但某些情况 for 循环更合适:需要在中间 break、需要索引进行复杂操作、或需要同时访问相邻元素。适合用高阶函数的场景:变换、筛选、归约、展平。
📖 小节
map对集合每个元素进行变换,返回长度相同的新数组filter根据条件保留元素,返回满足条件的新数组reduce将集合归约为单个值,需指定初始值和累加闭包compactMap在变换同时过滤nil,flatMap展平嵌套集合- 链式调用将多个高阶函数串联成数据流水线,代码简洁意图清晰
- 对大数据量可以使用
lazy延迟求值优化性能
📝 作业
- 基础题: 有一个整数数组
[10, 20, 30, 40, 50],先用map将每个元素除以 2,再用filter筛选出大于 15 的元素。 - 进阶题: 有一个字符串数组
["apple", "banana", "kiwi", "strawberry", "fig"],用链式调用筛选出长度大于等于 5 的字符串,转成大写,然后按字母顺序排序。 - 挑战题: 有一个
Transaction结构体数组(属性:amount: Double、category: String、date: Date),用reduce(into:)实现按category分组统计金额。然后输出各分类的总额。