Swift: Swiftの範囲とシーケンス:Range、ClosedRange、Stride
範囲は数直線上の区間のようなものです——AからBまでの連続した整数。このレッスンではSwiftのすべての範囲型とシーケンス型の作成、使用、カスタマイズ方法を学びます。
1. 学習目標
...と..<を使った範囲の作成と走査- strideを使ったカスタムステップでの値の走査
- Sequenceプロトコルに準拠したカスタムシーケンスの構築
- zipを使った2つのシーケンスの並行走査
- switch文と配列スライスでの範囲の実践的な適用
2. イベントプランナーの実話
(1) 課題:200席番号の手動生成 — Excelがフリーズ
Charlieは200人規模の技術カンファレンスを企画しています。座席番号を生成する必要があります:A列からT列(20列)、各列10席、参加者を座席に割り当て、座席表を作成します。 彼はExcelのフィルハンドルをドラッグして始めましたが、��IP席を1つ追加するたびにシート全体を再調整する必要がありました:
SWIFT
let seatA1 = "A1"
let seatA2 = "A2"
// ... 200 variables total
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分 | 数字を1つ変えるだけ |
| 間隔を空けた席 | 手動検索 | strideが直接生成 |
| エラー率 | 高い | 0 |
3. RangeとClosedRange
Range(半開 ..<)は開始値を含みますが終了値を含みません。ClosedRange(閉 ...)は両方の端点を含みます。
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 | 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から無限 | 配列スライス array[2...] |
| PartialThrough | ...b |
...からbまで | 配列スライス array[...2] |
(1) 範囲の作成と走査
SWIFT
// 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) 配列スライスでの範囲
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
// ============================================
// 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)")
}
出力:
TEXT 📖 参照専用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ずつ増やすのではなく、カスタムのステップ値で走査できます。カスタムシーケンスを使うと独自の反復ロジックを定義できます。
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 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) カスタムシーケンス
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
// ============================================
// 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))")
}
出力:
TEXT 📖 参照専用摂氏 華氏 ----------------- 0 32 10 50 20 68 ... (中間行は省略) 100 212 華氏 摂氏 ------------------- 212 100 192 88 ... (中間行は省略) 32 0
5. zipとシーケンスの組み合わせ
zipは2つのシーケンスをインデックスでペアにし、タプルのシーケンスを生成します。どちらかのシーケンスが終了すると自動的に停止します。
| 特性 | 説明 |
|---|---|
| 入力 | 2つのシーケンス |
| 出力 | タプル (要素1, 要素2) のシーケンス |
| 停止条件 | 最初に終了したシーケンス |
| 一般的な用途 | 並行走査、データペアリング、インデックス生成 |
(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
// ============================================
// 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)")
}
出力:
TEXT 📖 参照専用=== 座席割り当て === A1: Alice A2: Bob A3: Charlie A4: Diana B1: Eve B2: Frank === VIP席 === A1: Alice A2: Bob
6. 完全な例:試験座席表と成績分析
SWIFT
// ============================================
// 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))")
}
出力:
TEXT 📖 参照専用=== 試験座席表 === A1 A2 A3 A4 B1 B2 B3 B4 C1 C2 C3 C4 D1 D2 D3 D4 E1 E2 E3 E4 === 座席割り当て === A1: Alice A2: Bob ... (中間行は省略) E4: Tina === 成績レポート === Alice: 82 -- B Bob: 67 -- D ... (中間行は省略) === 成績分布 === A: ### (3) B: ##### (5) C: #### (4) D: ### (3) F: # (1)
❓ よくある質問
Q
...と..<の違いは?A
a...bはbを含みます(閉範囲)。a..<bはbを含みません(半開範囲)。例えば、1...3は1,2,3を走査し、1..<3は1,2を走査します。Q 範囲は非整数型にも使えますか?
A はい。Comparableに準拠する任意の型(DoubleやCharacterなど)で動作します:1.0...5.0や"a"..."z"。ただし、Double範囲はfor-inでは使えません(ステップの曖昧さのため)。
Q strideのbyパラメータは負の値にできますか?
A はい。負の値は降順の走査を作成します。fromとto/throughの順序を入れ替えることを忘れないでください——大きい方を先に、小さい方を後にします。
Q zipが返すシーケンスの長さは?
A 短い方の入力シーケンスの長さです。zipはどちらかのシーケンスが終了すると停止し、nilでパディングはしません。
Q 値が範囲内にあるかを確認するには?
A contains()メソッドを使用します:
(1...10).contains(5)はtrueを返します。またはswitchでマッチします:case 1...10:。Q
array[2...]での配列スライスは配列をコピーしますか?A いいえ。配列スライスは元の配列のメモリを共有するArraySliceを返します——コピーは行われません。スライスは独自のインデックス開始オフセットを持ちます。
📖 まとめ
- 閉範囲
...と半開範囲..<が基本的な範囲型 - 範囲はswitch caseで直接範囲マッチングに使用可能
- strideでカスタムステップ値を指定し、降順と浮動小数点の走査もサポート
- カスタムのSequenceとIteratorProtocolで独自の反復ロジックを定義可能
- zipで2つのシーケンスを並行ペアリングし、短い方が終了すると停止
[2...]と[..<3]での配列スライスで効率的な部分配列アクセスを提供
📝 練習問題
- 初級: 1...10を使って九九の3の段(31から310)を「3 x 1 = 3」の形式で出力してください。
- 中級: strideを使って100から0までの偶数を-2のステップで生成し、それらの合計を計算してください。
- 上級: カスタムシーケンスWeekdaySequenceを作成し、与えられた曜日(例:「水」)から始めて["月", "火", "水", "木", "金", "土", "日"]を無限にループさせてください。zipを使って最初の14個の整数とペアにして「1日目: 水」、「2日目: 木」...と出力してください。