Swift: Swift 区间与序列:Range 和 Stride
区间就像数学中的数轴片段——从 A 到 B 的连续整数。本课带你掌握 Swift 中所有区间和序列的创建、操作和自定义方法。
1. 你将学到
- 使用 ... 和 ..< 创建区间并遍历
- 使用 stride 实现自定义步进遍历
- 创建自定义序列并遵守 Sequence 协议
- 使用 zip 合并两个序列并行遍历
- 掌握区间在 switch 和数组切片中的实际应用
2. 一个活动策划师的故事
(1) 痛点:手动生成 200 个座位的编号,Excel 都要卡死了
Charlie 在策划一场 200 人的技术大会,需要为每个座位生成编号:行号 A-T(20 行),每行 10 个座位,还要把参会者分配到座位,并生成座位表。 他最开始手动在 Excel 里拖拽填充,但每加一个 VIP 座位就要重新调整整张表: `swift let seatA1 = "A1" let seatA2 = "A2" // ...一共 200 个变量 直到第 150 个座位时 Excel 崩溃,Charlie 意识到:需要程序化生成座位编号。
(2) 区间和 Stride 的解法
`swift let rows = "ABCDEFGHIJKLMNOPQRST" let seatsPerRow = 10 for row in rows { for seat in 1...seatsPerRow { print("(row)(seat)", terminator: " ") } print() }
(3) 收益:5 小时 → 3 秒
| 维度 | 拖拽 Excel | Swift 区间生成 |
|---|---|---|
| 200 个座位 | 5 小时 | 3 秒 |
| 修改座位布局 | 重新拖拽 10 分钟 | 改一个数字 |
| 定制间距座位 | 手动查找 | stride 直接生成 |
| 出错概率 | 高 | 0 |
3. Range 和 ClosedRange
Range(半开区间 ..<)包含起始值但不包含结束值。ClosedRange(闭区间 ...)包含两端。 `mermaid graph LR A["1...5"] --> B[1] A --> C[2] A --> D[3] A --> E[4] A --> F[5] G["1..<5"] --> H[1] G --> I[2] G --> J[3] G --> K[4]
| 区间类型 | 语法 | 包含 | 示例结果 |
|---|---|---|---|
| ClosedRange | ...b | a, a+1, ..., b | 1...3 -> 1,2,3 |
| Range | ..<b | a, a+1, ..., b-1 | 1..<3 -> 1,2 |
| 单侧区间 | ... | a 到无穷 | 数组切片 rray[2...] |
| 单侧区间 | ...b | ... 到 b | 数组切片 rray[...2] |
(1) 创建和遍历区间
`swift // 闭区间:包含 1 到 5 let closed = 1...5 for i in closed { print(i, terminator: " ") } print() // 半开区间:包含 1 到 4 let halfOpen = 1..<5 for i in halfOpen { print(i, terminator: " ") } print() // 反向遍历 for i in (1...5).reversed() { print(i, terminator: " ") } print()
(2) 区间在数组切片中的应用
`swift let numbers = [10, 20, 30, 40, 50, 60, 70] let firstThree = numbers[0..<3] print(firstThree) let fromSecond = numbers[2...] print(fromSecond) let firstFour = numbers[...3] print(firstFour)
▶ 示例:考试评分区间判断
`swift // ============================================ // 用区间在 switch 中做评分判断 // ============================================ let scores = [95, 82, 67, 54, 43, 78, 91] for score in scores { let grade: String switch score { case 90...100: grade = "A" case 80..<90: grade = "B" case 70..<80: grade = "C" case 60..<70: grade = "D" case 0..<60: grade = "F" default: grade = "Invalid" } print("Score (score): Grade (grade)") }
输出:
ext Score 95: Grade A Score 82: Grade B Score 67: Grade D Score 54: Grade F Score 43: Grade F Score 78: Grade C Score 91: Grade A
4. Stride 与自定义序列
Stride 让你可以按自定义步进值遍历,而不是每次加 1。自定义序列让你可以创建自己的遍历逻辑。 `mermaid graph TB A[Sequence] --> B["stride(from: 0, to: 10, by: 2)"] A --> C["stride(from: 10, through: 0, by: -2)"] B --> D["0, 2, 4, 6, 8"] C --> E["10, 8, 6, 4, 2, 0"]
| 函数 | 区间类型 | 是否包含终点 | 示例 |
|---|---|---|---|
| stride(from:to:by:) | 半开 | 不包含 | stride(from:0, to:10, by:3) -> 0,3,6,9 |
| stride(from:through:by:) | 闭区间 | 包含 | stride(from:0, through:10, by:3) -> 0,3,6,9 |
(1) Stride 基本用法
`swift // 0 到 9,步进 2 for i in stride(from: 0, to: 10, by: 2) { print(i, terminator: " ") } print() // 10 到 0,步进 -3 for i in stride(from: 10, through: 0, by: -3) { print(i, terminator: " ") } print() // 浮点数步进 for temp in stride(from: 0.0, to: 1.0, by: 0.25) { print("(Int(temp * 100))%", terminator: " ") } print()
(2) 自定义序列
`swift struct FibonacciSequence: Sequence { let count: Int func makeIterator() -> FibonacciIterator { return FibonacciIterator(count: count) } } struct FibonacciIterator: IteratorProtocol { let count: Int var current = 0 var nextValue = 1 var index = 0 mutating func next() -> Int? { guard index < count else { return nil } defer { index += 1 } let result = current current = nextValue nextValue = result + current return result } } let fib = FibonacciSequence(count: 10) for num in fib { print(num, terminator: " ") } print()
▶ 示例:温度转换表生成
`swift // ============================================ // 用 stride 生成温度转换表 // ============================================ print("Celsius\tFahrenheit") print("-----------------") for celsius in stride(from: 0.0, through: 100.0, by: 10.0) { let fahrenheit = celsius * 9 / 5 + 32 print("(Int(celsius))\t(Int(fahrenheit))") } print() print("Fahrenheit\tCelsius") print("-------------------") for f in stride(from: 212.0, through: 32.0, by: -20.0) { let c = (f - 32) * 5 / 9 print("(Int(f))\t\t(Int(c))") }
输出: ` ext Celsius Fahrenheit
0 32 10 50 20 68 ...(省略中间行) 100 212
Fahrenheit Celsius
212 100 192 88 ...(省略中间行) 32 0 `
5. zip 与序列组合
zip 将两个序列按索引配对,生成元组序列。当其中一个序列结束时,zip 自动停止。
| 特性 | 说明 |
|---|---|
| 输入 | 两个序列 |
| 输出 | 元组序列 (Element1, Element2) |
| 停止条件 | 任一序列结束 |
| 常见用途 | 并行遍历、配对数据、生成序号 |
(1) zip 基本用法
`swift let names = ["Alice", "Bob", "Charlie", "Diana"] let scores = [88, 92, 75, 95] for (name, score) in zip(names, scores) { print("(name): (score)") } let short = ["A", "B", "C"] let long = [1, 2, 3, 4, 5] for pair in zip(short, long) { print(pair) }
(2) zip + 区间生成索引
`swift let fruits = ["Apple", "Banana", "Orange", "Grape"] for (index, fruit) in zip(1..., fruits) { print("(index). (fruit)") }
▶ 示例:座位分配系统
`swift // ============================================ // 用 zip 和区间为参会者分配座位 // ============================================ let attendees = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"] let rows = ["A", "B", "C", "D"] let seatsPerRow = 4 var allSeats: [String] = [] for row in rows { for seat in 1...seatsPerRow { allSeats.append("(row)(seat)") } } print("=== Seat Assignments ===") for (attendee, seat) in zip(attendees, allSeats) { print("(seat): (attendee)") } let vipCount = 2 let vipSeats = allSeats.prefix(vipCount) let vipAttendees = attendees.prefix(vipCount) print("\n=== VIP Section ===") for (person, seat) in zip(vipAttendees, vipSeats) { print("(seat): (person)") }
输出: ` ext === Seat Assignments === A1: Alice A2: Bob A3: Charlie A4: Diana B1: Eve B2: Frank
=== VIP Section === A1: Alice A2: Bob `
6. 完整示例:考试座位表与成绩分析系统
`swift // ============================================ // 考试座位表与成绩分析 // 综合运用 Range / Stride / zip / Sequence // ============================================ import Foundation let examRows = 5 let cols = 4 print("=== Exam Seating Chart ===") for row in 0..<examRows { let rowLabel = Character(UnicodeScalar(65 + row)!) for col in 1...cols { print("(rowLabel)(col)", terminator: "\t") } print() } let students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack", "Kevin", "Leo", "Mia", "Noah", "Olivia", "Paul", "Quinn", "Rose", "Sam", "Tina"] let examSeats = examRows * cols let assignedIndices = stride(from: 0, to: min(students.count, examSeats), by: 1) print("\n=== Seat Assignments ===") for (index, studentIndex) in zip(assignedIndices, 0..<students.count) { let row = index / cols let col = index % cols + 1 let rowLabel = Character(UnicodeScalar(65 + row)!) print("(rowLabel)(col): (students[studentIndex])") } print("\n=== Score Report ===") var scores: [String: Int] = [:] for student in students.prefix(examSeats) { scores[student] = Int.random(in: 40...100) } let gradeRanges: [ClosedRange<Int>: String] = [ 90...100: "A", 80...89: "B", 70...79: "C", 60...69: "D", 0...59: "F" ] var gradeCount: [String: Int] = ["A": 0, "B": 0, "C": 0, "D": 0, "F": 0] for (student, score) in scores.sorted(by: { .key < .key }) { var grade = "F" for (range, g) in gradeRanges { if range.contains(score) { grade = g break } } gradeCount[grade]! += 1 print("(student): (score) -- (grade)") } print("\n=== Grade Distribution ===") for grade in ["A", "B", "C", "D", "F"] { let count = gradeCount[grade]! let bar = String(repeating: "#", count: count) print("(grade): (bar) ((count))") }
输出: ` ext === Exam Seating Chart === A1 A2 A3 A4 B1 B2 B3 B4 C1 C2 C3 C4 D1 D2 D3 D4 E1 E2 E3 E4
=== Seat Assignments === A1: Alice A2: Bob ...(省略中间行) E4: Tina
=== Score Report === Alice: 82 -- B Bob: 67 -- D ...(省略中间行)
=== Grade Distribution === A: ### (3) B: ##### (5) C: #### (4) D: ### (3) F: # (1) `
❓ 常见问题
📖 小节
- 闭区间 ... 和半开区间 ..< 是最基本的区间类型
- 区间可以直接用在 switch 的 case 中做范围匹配
- stride 允许自定义步进值,支持递减和浮点数
- 自定义 Sequence 和 IteratorProtocol 可以创建自己的遍历逻辑
- zip 将两个序列并行配对,在最短序列结束时停止
- 数组切片 [2...] 和 [..<3] 高效地访问子数组
📝 作业
- 基础题: 用 1...10 打印九九乘法表的第 3 行(31 到 310),输出格式为 "3 x 1 = 3"。
- 进阶题: 用 stride 生成从 100 到 0 的偶数序列,步进为 -2,计算这些偶数的总和。
- 挑战题: 创建一个自定义序列 WeekdaySequence,从指定星期几(如 "Wed")开始,无限循环输出 ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]。用 zip 与前 14 个整数配对,打印出 "Day 1: Wed", "Day 2: Thu" ...