Swift: Swift Ranges and Sequences
A range is like a segment on a number line — consecutive integers from A to B. This lesson covers how to create, use, and customize all range and sequence types in Swift.
1. What You'll Learn
- Create and traverse ranges with
...and..< - Step through values with custom strides using stride
- Build custom sequences conforming to the Sequence protocol
- Pair up two sequences for parallel traversal with zip
- Apply ranges in switch statements and array slicing in practice
2. An Event Planner's Real Story
(1) Pain: Manually generating 200 seat numbers — Excel froze
Charlie is organizing a 200-person tech conference. He needs to generate seat numbers: rows A through T (20 rows), 10 seats per row, assign attendees to seats, and produce a seating chart. He started by dragging the fill handle in Excel, but every time he added a VIP seat he had to readjust the entire sheet:
let seatA1 = "A1"
let seatA2 = "A2"
// ... 200 variables total
By seat #150, Excel crashed. Charlie realized: he needed programmatic seat number generation.
(2) The Range and Stride Solution
let rows = "ABCDEFGHIJKLMNOPQRST"
let seatsPerRow = 10
for row in rows {
for seat in 1...seatsPerRow {
print("\(row)\(seat)", terminator: " ")
}
print()
}
(3) Result: 5 hours → 3 seconds
| Metric | Dragging in Excel | Swift Range Generation |
|---|---|---|
| 200 seats | 5 hours | 3 seconds |
| Change seating layout | Re-drag 10 minutes | Change one number |
| Spaced seats | Manual lookup | stride generates directly |
| Error rate | High | 0 |
3. Range and ClosedRange
Range (half-open ..<) includes the start value but excludes the end. ClosedRange (closed ...) includes both endpoints.
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]
| Range Type | Syntax | Includes | Example Result |
|---|---|---|---|
| ClosedRange | a...b |
a, a+1, ..., b | 1...3 -> 1,2,3 |
| Range | a..<b |
a, a+1, ..., b-1 | 1..<3 -> 1,2 |
| PartialFrom | a... |
a to infinity | Array slice array[2...] |
| PartialThrough | ...b |
... to b | Array slice array[...2] |
(1) Creating and Traversing Ranges
// Closed range: includes 1 through 5
let closed = 1...5
for i in closed {
print(i, terminator: " ")
}
print()
// Half-open range: includes 1 through 4
let halfOpen = 1..<5
for i in halfOpen {
print(i, terminator: " ")
}
print()
// Reverse traversal
for i in (1...5).reversed() {
print(i, terminator: " ")
}
print()
(2) Ranges in Array Slicing
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)
▶ Example: Exam Grading with Range Matching
// ============================================
// Using ranges in switch for grade classification
// ============================================
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)")
}
Output:
TEXT 📖 Display onlyScore 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 and Custom Sequences
Stride lets you traverse with a custom step value rather than incrementing by 1 every time. Custom sequences let you define your own iteration logic.
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"]
| Function | Range Type | Endpoint Inclusive | Example |
|---|---|---|---|
| stride(from:to:by:) | Half-open | No | stride(from:0, to:10, by:3) -> 0,3,6,9 |
| stride(from:through:by:) | Closed | Yes | stride(from:0, through:10, by:3) -> 0,3,6,9 |
(1) Basic Stride Usage
// 0 to 9, step 2
for i in stride(from: 0, to: 10, by: 2) {
print(i, terminator: " ")
}
print()
// 10 to 0, step -3
for i in stride(from: 10, through: 0, by: -3) {
print(i, terminator: " ")
}
print()
// Floating-point stepping
for temp in stride(from: 0.0, to: 1.0, by: 0.25) {
print("\(Int(temp * 100))%", terminator: " ")
}
print()
(2) Custom Sequences
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()
▶ Example: Temperature Conversion Table Generator
// ============================================
// Generate temperature conversion table with 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))")
}
Output:
TEXT 📖 Display onlyCelsius Fahrenheit ----------------- 0 32 10 50 20 68 ... (intermediate rows omitted) 100 212 Fahrenheit Celsius ------------------- 212 100 192 88 ... (intermediate rows omitted) 32 0
5. zip and Sequence Combination
zip pairs two sequences by index, producing a sequence of tuples. It stops automatically when either sequence ends.
| Characteristic | Description |
|---|---|
| Input | Two sequences |
| Output | Sequence of tuples (Element1, Element2) |
| Stop condition | Whichever sequence ends first |
| Common uses | Parallel traversal, data pairing, index generation |
(1) Basic zip Usage
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 + Range for Index Generation
let fruits = ["Apple", "Banana", "Orange", "Grape"]
for (index, fruit) in zip(1..., fruits) {
print("\(index). \(fruit)")
}
▶ Example: Seat Assignment System
// ============================================
// Assign attendees to seats using zip and ranges
// ============================================
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)")
}
Output:
TEXT 📖 Display only=== Seat Assignments === A1: Alice A2: Bob A3: Charlie A4: Diana B1: Eve B2: Frank === VIP Section === A1: Alice A2: Bob
6. Full Example: Exam Seating Chart and Grade Analysis
// ============================================
// Exam seating chart and grade analysis
// Combining 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: { $0.key < $1.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))")
}
Output:
TEXT 📖 Display only=== 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 ... (intermediate rows omitted) E4: Tina === Score Report === Alice: 82 -- B Bob: 67 -- D ... (intermediate rows omitted) === Grade Distribution === A: ### (3) B: ##### (5) C: #### (4) D: ### (3) F: # (1)
❓ FAQ
... and ..<?a...b includes b (closed range). a..<b excludes b (half-open range). For example, 1...3 traverses 1,2,3; 1..<3 traverses 1,2.(1...10).contains(5) returns true. Or match in a switch: case 1...10:.array[2...] copy the array?📖 Summary
- Closed range
...and half-open range..<are the fundamental range types - Ranges can be used directly in switch case for range matching
- stride allows custom step values and supports both descending and floating-point traversal
- Custom Sequence and IteratorProtocol let you define your own iteration logic
- zip pairs two sequences in parallel, stopping when the shorter one ends
- Array slicing with
[2...]and[..<3]provides efficient sub-array access
📝 Exercises
- Beginner: Use 1...10 to print the 3rd row of a multiplication table (31 through 310), formatted as "3 x 1 = 3".
- Intermediate: Use stride to generate even numbers from 100 down to 0 with a step of -2, and calculate their sum.
- Challenge: Create a custom sequence WeekdaySequence that starts from a given weekday (e.g. "Wed") and loops indefinitely through ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]. Use zip to pair with the first 14 integers and print "Day 1: Wed", "Day 2: Thu"...