Swift: SwiftのSetと辞書チュートリアル:集合演算とDictionaryの使い方を解説
Setは色付きビー玉の袋のようなもの——各色1つずつで、特に順序はなく混ざっています。Dictionaryは電話帳のようなもの——名前ですばやく番号を調べられます。このレッスンではこの2つの強力なデータ構造を学びます。
1. 学習目標
- Setの作成と操作(積集合、和集合など)
- キー・値ストレージのためのDictionaryの作成と操作
- Hashableプロトコルとカスタム型をキーとして使用する方法の理解
- 効率的な重複排除と所属チェックのためのSetの使用
- よく使うメソッドでのDictionaryの走査と変更
2. バックエンドエンジニアの実話
(1) 課題:ユーザータグ重複排除にif-elseを500行 — 痛いほど遅い
Bobはユーザープロファイリングシステムを構築しています。各ユーザーは複数のタグ(例:「VIP」「プロモ敏感」「高額支出者」)を持ち、広範な集合演算が必要です:VIPかつ高額支出者のユーザーを見つける、新旧タグをマージする、ブラックリストタグを除外する。 彼は配列とループベースの重複排除から始めました:
SWIFT
let oldTags = ["VIP", "HighSpender", "NewUser"]
let newTags = ["VIP", "PromoSensitive", "HighSpender"]
var merged: [String] = []
for tag in oldTags + newTags {
if !merged.contains(tag) {
merged.append(tag)
}
}
20万ユーザー × O(n^2)アルゴリズム = サーバーCPUが20分間100%に。Bobは運用チームから苦情を受けました。
(2) SetとDictionaryの解決策
SWIFT
let oldTags: Set = ["VIP", "HighSpender", "NewUser"]
let newTags: Set = ["VIP", "PromoSensitive", "HighSpender"]
let merged = oldTags.union(newTags)
let common = oldTags.intersection(newTags)
print("Merged: \(merged)")
print("Common: \(common)")
(3) 結果:20分 → 0.5秒
| 指標 | 配列ループ | Set/Dictionary |
|---|---|---|
| 20万ユーザーの重複排除 | 20分 | 0.5秒 |
| コード行数 | 500+ | 30 |
| メモリ使用量 | 200 MB | 45 MB |
| 和集合操作 | 手書きループ | .union() 1行 |
3. Set
Setは一意な要素の順序なしコレクションです。配列は「順序と重複」を重視し、Setは「一意性と所属」を重視します。
graph TB
A[集合 A] --- B["{1, 2, 3}"]
C[集合 B] --- D["{2, 3, 4}"]
E[和集合] --- F["{1, 2, 3, 4}"]
G[積集合] --- H["{2, 3}"]
I[対称差] --- J["{1, 4}"]
K[差集合] --- L["A - B = {1}"]
| 集合演算 | Swiftメソッド | 結果 |
|---|---|---|
| 和集合 | union(_:) | 両方の集合のすべての要素 |
| 積集合 | intersection(_:) | 両方の集合に共通する要素 |
| 差集合 | subtracting(_:) | Aに含まれBに含まれない要素 |
| 対称差 | symmetricDifference(_:) | ちょうど一方の集合にのみ含まれる要素 |
| 部分集合か | isSubset(of:) | Aの全要素がBに含まれる |
| 含むか | contains(_:) | O(1)の所属チェック |
(1) 作成と基本操作
SWIFT
var fruits: Set<String> = ["Apple", "Banana", "Orange"]
fruits.insert("Apple")
fruits.insert("Grape")
fruits.remove("Banana")
print(fruits.contains("Apple"))
print(fruits.count)
(2) 集合演算
SWIFT
let a: Set = [1, 2, 3, 4, 5]
let b: Set = [4, 5, 6, 7, 8]
print("Union: \(a.union(b).sorted())")
print("Intersection: \(a.intersection(b).sorted())")
print("A - B: \(a.subtracting(b).sorted())")
print("Symmetric Diff: \(a.symmetricDifference(b).sorted())")
▶ サンプル: ユーザータグ管理システム
SWIFT
// ============================================
// Managing user tags with Sets
// ============================================
var userTags: Set<String> = ["VIP", "NewUser", "HighSpender"]
let campaignTags: Set = ["VIP", "PromoSensitive"]
let excludeTags: Set = ["Inactive", "Fraud"]
userTags.insert("iOSUser")
userTags.insert("VIP")
let targetUsers = campaignTags.subtracting(excludeTags)
print("Target tags: \(targetUsers)")
let vipHighSpender = userTags.intersection(["VIP", "HighSpender"])
print("VIP high spenders: \(vipHighSpender)")
let allActive = userTags.union(campaignTags).subtracting(excludeTags)
print("All active tags: \(allActive.sorted())")
出力:
TEXT 📖 参照専用Target tags: ["VIP", "PromoSensitive"] VIP high spenders: ["VIP", "HighSpender"] All active tags: ["HighSpender", "iOSUser", "NewUser", "PromoSensitive", "VIP"]
4. Dictionary
Dictionaryはキーと値のペアの順序なしコレクションで、各キーは一意に値にマッピングされます。「名前で検索」のシナリオに最適です。
graph TB
A[Dictionary] --> B["キー: りんご -> 値: 3"]
A --> C["キー: バナナ -> 値: 5"]
A --> D["キー: オレンジ -> 値: 2"]
B --> E[キーによるO(1)検索]
| 操作 | 構文 | 備考 |
|---|---|---|
| 作成 | キー型: 値型 | 空の辞書 |
| リテラル | ["a": 1, "b": 2] | 初期値付き |
| 読み取り | dict["キー"] | オプショナルを返す |
| 代入 | dict["キー"] = 値 | 追加または更新 |
| 削除 | dict["キー"] = nil | キーと値のペアを削除 |
| 走査 | for (k, v) in dict |
すべてを反復処理 |
(1) 作成と変更
SWIFT
var scores: [String: Int] = [:]
var populations = [
"New York": 8_336_817,
"Los Angeles": 3_979_576,
"Chicago": 2_693_976
]
populations["Houston"] = 2_320_268
populations["New York"] = 8_400_000
populations["Chicago"] = nil
if let nyPopulation = populations["New York"] {
print("NY population: \(nyPopulation)")
}
(2) Dictionaryの走査
SWIFT
let capitals = [
"USA": "Washington DC",
"UK": "London",
"Japan": "Tokyo",
"France": "Paris"
]
for (country, city) in capitals {
print("\(country): \(city)")
}
print("Countries: \(capitals.keys.sorted())")
print("Capitals: \(capitals.values.sorted())")
▶ サンプル: 在庫管理
SWIFT
// ============================================
// Managing inventory with Dictionary
// ============================================
var inventory: [String: Int] = [
"Laptop": 15,
"Mouse": 50,
"Keyboard": 30
]
inventory["Monitor"] = 10
inventory["Mouse"]! += 20
if let laptopStock = inventory["Laptop"], laptopStock > 0 {
inventory["Laptop"] = laptopStock - 1
print("Shipped 1 Laptop")
}
let product = "Tablet"
if let stock = inventory[product] {
print("\(product): \(stock) units")
} else {
print("\(product) not found in inventory")
}
print("\n=== Inventory Report ===")
for (product, quantity) in inventory {
let status = quantity < 20 ? "Low stock" : "In stock"
print("\(product): \(quantity) units -- \(status)")
}
出力:
TEXT 📖 参照専用ノートPC 1台を出荷 タブレット は在庫にありません === 在庫レポート === ノートPC: 14 台 -- 在庫僅少 マウス: 70 台 -- 在庫あり キーボード: 30 台 -- 在庫あり モニター: 10 台 -- 在庫僅少
5. Hashableプロトコルとネストされたコレクション
DictionaryのキーとSetの要素はHashableプロトコルに準拠する必要があります——Swiftはハッシュ値を使って高速検索を行います。
| 組み込みのHashable型 | 手動準拠が必要な場合 |
|---|---|
| String, Int, Double, Bool | カスタム構造体/クラス |
| 配列(hashable要素を含む) | 関連値を持つ列挙型 |
| Set, Dictionary(値として) | 非hashableプロパティを含む型 |
(1) カスタム型をキーとして使用
SWIFT
struct Product: Hashable {
let id: Int
let name: String
}
var cart: [Product: Int] = [:]
let laptop = Product(id: 1001, name: "Laptop")
cart[laptop] = 2
print("Cart items: \(cart.count)")
(2) ネストされたコレクション: SetのDictionary
SWIFT
var cityTags: [String: Set<String>] = [
"Paris": ["Eiffel Tower", "Louvre"],
"Tokyo": ["Shibuya", "Sensoji"]
]
cityTags["Paris"]?.insert("Arc de Triomphe")
cityTags["London"] = ["Big Ben", "Tower Bridge"]
for (city, landmarks) in cityTags {
print("\(city): \(landmarks.sorted().joined(separator: ", "))")
}
▶ サンプル: ユーザーグループ統計
SWIFT
// ============================================
// User group stats with Dictionary and Set
// ============================================
let userLanguages: [String: Set<String>] = [
"Alice": ["Swift", "Python", "JavaScript"],
"Bob": ["Python", "Java", "Go"],
"Charlie": ["Swift", "Kotlin", "JavaScript"],
"Diana": ["Java", "C#", "Python"]
]
let swiftUsers = userLanguages.filter { $0.value.contains("Swift") }
print("Swift developers: \(swiftUsers.count)")
let allLanguages = userLanguages.values.reduce([]) { $0.union($1) }
print("All languages: \(allLanguages.sorted())")
let fullStack = userLanguages.filter { $0.value.count >= 3 }
for (name, langs) in fullStack {
print("Full stack: \(name) -- \(langs.sorted().joined(separator: ", "))")
}
出力:
TEXT 📖 参照専用Swift developers: 2 All languages: ["C#", "Go", "Java", "JavaScript", "Kotlin", "Python", "Swift"] Full stack: Alice -- JavaScript, Python, Swift Full stack: Charlie -- JavaScript, Kotlin, Swift Full stack: Diana -- C#, Java, Python
6. 完全な例:ユーザープロフィールタグ付けシステム
SWIFT
// ============================================
// User profile tagging system
// Combining Set and Dictionary concepts
// ============================================
import Foundation
struct UserProfile: Hashable {
let id: Int
let name: String
var tags: Set<String>
}
var users: [Int: UserProfile] = [
1: UserProfile(id: 1, name: "Alice", tags: ["VIP", "HighSpender", "iOS"]),
2: UserProfile(id: 2, name: "Bob", tags: ["NewUser", "Android"]),
3: UserProfile(id: 3, name: "Charlie", tags: ["VIP", "Android", "HighSpender"]),
4: UserProfile(id: 4, name: "Diana", tags: ["iOS", "NewUser"])
]
let campaignTags: Set = ["VIP", "iOS"]
let excludeTags: Set = ["Fraud", "Inactive"]
var targetUserIds: Set<Int> = []
for (id, profile) in users {
let effectiveTags = profile.tags.subtracting(excludeTags)
if !effectiveTags.intersection(campaignTags).isEmpty {
targetUserIds.insert(id)
}
}
print("=== Campaign Target Users ===")
for id in targetUserIds.sorted() {
if let user = users[id] {
print("\(user.name) -- tags: \(user.tags.sorted().joined(separator: ", "))")
}
}
print("\n=== Adding Tag: BetaTester ===")
for id in users.keys {
users[id]?.tags.insert("BetaTester")
}
print("\n=== Tag Distribution ===")
var tagCounts: [String: Int] = [:]
for (_, profile) in users {
for tag in profile.tags {
tagCounts[tag, default: 0] += 1
}
}
for (tag, count) in tagCounts.sorted(by: { $0.value > $1.value }) {
print("\(tag): \(count) users")
}
出力:
TEXT 📖 参照専用=== キャンペーン対象ユーザー === Alice -- タグ: VIP、iOS、高額支出者 Charlie -- タグ: VIP、Android、高額支出者 Diana -- タグ: iOS、新規ユーザー === タグ追加: ベータテスター === === タグ分布 === ベータテスター: 4 人 Android: 2 人 高額支出者: 2 人 iOS: 2 人 新規ユーザー: 2 人 VIP: 2 人
❓ よくある質問
Q Setと配列の使い分けは?
A 一意性、高速検索(O(1))が必要で、順序を気にしない場合はSetを使用します。順序が必要、重複を許容、インデックスベースのアクセスが必要な場合は配列を使用します。
Q Dictionaryでキーが見つからない場合、何が返りますか?
A nil(オプショナル)が返ります。辞書の検索は常にif-letまたは??演算子で処理してください。
Q Hashableとは何で、なぜSetとDictionaryのキーに必要なのですか?
A Hashableは型がハッシュ化できることを意味します。Swiftはハッシュ値を使って要素を素早く特定します(O(1)の計算量)。Int、Stringなどの基本型はデフォルトでHashableに準拠しています。
Q カスタム構造体を辞書のキーとして使用する際の注意点は?
A Hashableに準拠する必要があります。すべての格納プロパティがHashableであれば、Swiftが自動的にハッシュメソッドを合成します。そうでない場合はhash(into:)と==を手動で実装してください。
Q Dictionaryのkeysとvaluesプロパティは何を返しますか?
A コレクション型を返します——keysはDictionary.Keys(Setのように使用可能)、valuesはDictionary.Values(配列のように使用可能)。どちらも直接走査できます。
📖 まとめ
- Setは順序なしの一意要素コレクションで、重複排除と所属チェックに最適
- Setはunion、intersection、subtracting、symmetricDifferenceの演算をサポート
- Dictionaryは順序なしのキー・値ペアコレクションで、読み取りはオプショナルを返す
- DictionaryのキーとSetの要素はHashableに準拠する必要がある
- 値を代入して辞書エントリを追加し、nilを設定して削除
- ネストされたコレクション(SetのDictionary)で複雑なデータモデリングが可能
📝 練習問題
- 初級: 好きな本5冊のSetを作成してください。「Swiftプログラミング」が含まれているかチェックし、新しい本を2冊追加して、最終的なSetを出力してください。
- 中級: Dictionaryを使って簡単な英和辞書を実装してください。5つの単語と訳語を追加し、検索関数(英語入力→日本語返却)を実装し、単語が見つからない場合の処理も行ってください。
- 上級: データセットを分析: ["Alice": ["Swift", "Python"], "Bob": ["Java", "Swift"], "Charlie": ["Python", "Go"], "Diana": ["Swift", "Go"]]。Swiftを知っているがGoを知らないユーザー、PythonまたはJavaを知っているユーザー、すべての言語の集合(重複排除)、言語ごとのユーザー数を求めてください。