개발/Swift
Swift에서 Set를 사용해서 포함(contains) 검사
까칠코더
2025. 11. 11. 12:23
반응형
Swift에서 Set를 사용해서 포함(contains) 검사
의도·성능·실무 예제를 중심으로 왜 Array 대신 Set를 써야 하는지, 그리고 어떻게 안전하게 쓰는지를 정리했습니다.
1. 핵심 요약
| 항목 | Set | Array | Dictionary |
| 포함 검사 contains(_:) | 평균 O(1) (해시) | O(n) | 키 존재 확인 O(1) |
| 중복 허용 | 불가(자동 제거) | 가능 | 키는 불가, 값은 가능 |
| 순서 보존 | 보장 안 함 | 보통 삽입 순서 | 키 순서 보장 안 함 |
| 대표 용도 | 화이트/블랙리스트, 빠른 조회, 중복 제거 | 순서가 중요한 목록, UI 표시 | 키 ↔ 값 매핑 |
요점: “포함되어 있나?”만 빠르게 확인할 땐 Set이 최적입니다.
2. 언제 Set를 써야 하나?
- 화이트리스트/블랙리스트로 허용/차단 여부를 빠르게 판단
- 대량 데이터 중복 제거 (Unique 보장)
- 교집합/차집합 등 집합 연산이 핵심일 때
- 키-값 매핑이 필요 없다면 Dictionary보다 가볍고 간결
3. 기본 문법
// 생성
let allowed: Set<String> = ["admin", "editor", "guest"]
var visited = Set<Int>()
// 기본 연산
allowed.contains("editor") // true (평균 O(1))
visited.insert(42) // (inserted: Bool, memberAfterInsert: Int)
visited.remove(42) // Optional(42) 또는 nil
visited.update(with: 100) // 존재하면 교체, 없으면 삽입
visited.isEmpty
visited.count
순서가 필요하면?
- Array(set)로 배열로 변환하거나, 정렬이 필요하면 Array(set).sorted()를 사용하세요.
- 삽입 순서를 보존해야 한다면 Swift Collections 패키지의 OrderedSet을 고려하세요.
4. 실무 예제
4.1 화이트리스트/블랙리스트
let whitelist: Set<String> = ["KR", "US", "JP"]
let blacklist: Set<String> = ["RU", "IR"]
func canAccess(country: String) -> Bool {
if blacklist.contains(country) { return false }
return whitelist.contains(country)
}
4.2 중복 제거(데이터 클린업)
let raw = ["a", "b", "a", "c", "b", "d"]
let unique = Array(Set(raw)) // 순서 보존 X
let uniqueSorted = Array(Set(raw)).sorted()
4.3 빠른 교집합/차집합
let a: Set<Int> = [1,2,3,4,5]
let b: Set<Int> = [4,5,6,7]
let inter = a.intersection(b) // {4,5}
let uni = a.union(b) // {1,2,3,4,5,6,7}
let diff = a.subtracting(b) // {1,2,3}
let sym = a.symmetricDifference(b) // {1,2,3,6,7}
4.4 대량 포함 검사 최적화 (Array → Set로 승격)
let emails = loadEmails() // [String] (대량)
let banned = Set(loadBannedEmails()) // Set으로 보관
let filtered = emails.filter { !banned.contains($0) } // 매우 빠름
4.5 앱 권한/플래그 토글
var enabledFlags: Set<String> = ["analytics", "push"]
func toggle(_ flag: String) {
if !enabledFlags.insert(flag).inserted {
enabledFlags.remove(flag)
}
}
4.6 교차 여부만 빠르게(조기 종료)
let required: Set<String> = ["camera", "microphone"]
let granted: Set<String> = ["camera"]
let missing = required.subtracting(granted) // {"microphone"}
if missing.isEmpty { /* 모두 허용됨 */ }
5. 커스텀 타입에서 Hashable 구현
Set에 담을 타입은 Hashable이어야 합니다. (Equatable도 자동 요구됨)
struct User: Hashable {
let id: Int
var name: String
// Swift 4.1+에서는 멤버별 Hashable 자동 합성 (아래 수동 구현 불필요)
// static func == (lhs: User, rhs: User) -> Bool { lhs.id == rhs.id }
// func hash(into hasher: inout Hasher) { hasher.combine(id) }
}
let u1 = User(id: 1, name: "A")
let u2 = User(id: 1, name: "B") // id만 같아도 동일 원소로 간주됨(자동 합성 규칙에 따름)
let s: Set<User> = [u1, u2] // {User(id:1,name:"A")} 1개
동등성 기준을 id만으로 하고 싶다면 Equatable/Hashable를 명시적으로 구현하여 의도를 분명히 하세요.
6. 성능 메모
- contains, insert, remove, update는 평균 O(1), 최악 O(n) (충돌 상황).
- 큰 데이터 병합은 formUnion(_:), formIntersection(_:), subtract(_:) 같은 in-place 연산이 더 빠릅니다.swift var s1: Set<Int> = [1,2,3] let s2: Set<Int> = [3,4,5] s1.formUnion(s2) // s1 = {1,2,3,4,5}
7. Set vs Array vs Dictionary 선택 가이드
| 상황 | 추천 | 이유 |
| 단순 멤버십(포함/제외) | Set | 평균 O(1) 조회 |
| 키‑값 매핑 필요 | Dictionary | 값 접근 O(1) |
| 순서가 중요/중복 허용 | Array | UI 표시, 정렬/인덱스 접근 |
| 순서 + 빠른 포함 검사 | OrderedSet (Swift Collections) | 순서 유지 + 해시 |
8. 고급 주의사항
- 순서가 필요하면 Array로 변환·정렬: Set 자체는 순서가 없습니다.
- Hashable 안정성: 해시와 동등성 기준은 논리적으로 불변이어야 합니다. (변경 가능한 속성으로 해시를 만들면 Set 무결성 문제)
- 브리징(NSSet): Obj‑C와 상호 운용 시 Set ↔ NSSet 브리징이 일어납니다. 타입 캐스팅/브리징 비용에 유의하세요.
- 메모리: 중복 없는 큰 데이터는 Set가 더 작을 수 있으나, 해시 테이블 오버헤드가 있어 작은 데이터에서는 차이가 미미할 수 있습니다.
9. 간단 벤치마크 스케치
import Foundation
let universe = (0..<2_000_00).map { "u\($0)" }
let needle = Set((0..<50_000).map { "u\($0 * 3)" }) // 1/3 주기로 포함
let arrayNeedle = Array(needle)
func measure(_ name: String, _ block: () -> Void) {
let t1 = CFAbsoluteTimeGetCurrent(); block(); let t2 = CFAbsoluteTimeGetCurrent()
print(name, ":", t2 - t1, "sec")
}
// Set 멤버십
measure("Set.contains") {
_ = universe.filter { needle.contains($0) }
}
// Array 멤버십
measure("Array.contains") {
_ = universe.filter { arrayNeedle.contains($0) } // 선형 탐색
}
일반적으로 Set.contains가 큰 차이로 빠릅니다. (환경/최적화에 따라 수치는 달라집니다)
10. 참조 링크
- Set 개요: https://developer.apple.com/documentation/swift/set
- 포함 검사 contains(_:): https://developer.apple.com/documentation/swift/set/1541179-contains
- 집합 연산: https://developer.apple.com/documentation/swift/set/algebraic-operations
- in‑place 연산(formUnion 등): https://developer.apple.com/documentation/swift/set/2995434-formunion
- Hashable: https://developer.apple.com/documentation/swift/hashable
- Swift Collections (OrderedSet): https://github.com/apple/swift-collections
11. 결론
- 포함 검사 목적이라면 Array 대신 Set이 정답입니다.
- 순서가 필요 없고, 중복을 막고 싶으며, 큰 데이터에서 빠른 조회가 중요하다면 Set이 가장 단순하고 확실한 선택입니다.
- 교집합/차집합/대칭차 등 집합 연산은 읽기 좋고 빠르게 문제를 풀어 줍니다.
반응형