Search

[Swift] Collection Types - Set

The Swift Programming Language Documents를 참고해서 정리했습니다. 여러 가지 생각들이 버무러져 있습니다.
contents
*이전 포스팅은 [Swift] Collection Types - Array 에서 보실 수 있습니다.

Collection Types

Swift에서는 Value 콜렉션을 저장하기 위한 3가지 기본 타입을 제공합니다.
Array : 순서가 지정된 값 콜렉션
Set : 고유값의 순서가 지정되지 않은 콜렉션
Dictionary : Key-Value 연결의 순서가 지정되지 않은 콜렉션
3가지 Collection은 저장할 수 있는 Value, Key 타입이 항상 명확합니다. 이는 잘못된 타입의 값을 실수로 Collection에 삽입할 수 없다는걸 의미합니다. 또한, Collection에서 값을 찾을 때 값이 가지는 타입에 대해 확신할 수 있음을 의미합니다.
Collection Type을 변수에 할당하게 되면, Collection 내부에 Item를 추가, 제거, 변경하는 방식으로 Collection를 변경할 수 있게 됩니다. 하지만, 상수에 할당하게 되면 Collection의 크기나 내용을 변경할 수 없게 됩니다.
var shoppingList: [String] = ["Six Eggs", "Milk", "Flour", "Bananas"] let genres: Set<String> = ["Rock", "Jazz", "Classical", "Hip Hop"]
Swift
복사
Collection를 초기화한 후에 변경할 필요가 없다면, 상수로 선언해서 immutable로 만드는 것이 좋습니다.
상수로 선언된 Collection은 코드를 쉽게 추론 가능하게 하고, Swift 컴파일러가 작성된 Collection의 성능 최적화 가능하게 만들어 줍니다.

Set

Set은 정의된 순서가 없는 동일한 타입의 고유한 값을 저장하는 콜렉션입니다.
요소들의 순서가 중요하지 않고, 요소가 콜렉션 안에서 중복없이 한 번만 나타나도록 해야할 때 사용하게 됩니다.
일단, Set의 선언 방식부터 알아봅시다.
Set은 배열과는 달리 축약 형식이 없습니다. ⓵ 기본형, ⓶ 타입 추론을 사용해서 선언을 합니다.
⓵ 기본형
var shoppingList = Set<String>()
Swift
복사
⓶ 타입 추론
var shoppingList: Set<String> = ["Six Eggs", "Milk", "Flour", "Bananas"] // [] 내부 값의 타입으로 Set Element의 타입 추론
Swift
복사
Set을 어떻게 만드는지 알았으니 이제 해당 Set에 새로운 물건을 넣어보고 삭제해보기도 해볼게요.
위에서 선언해둔 shoppingList를 사용해볼게요.
⓶ 타입 추론으로 만든 Set에 방금 산 사과와 아이스크림을 추가해볼게요.
shoppingList.insert("Apple") shoppingList.insert("Ice cream")
Swift
복사
Set은 배열과는 다르게 append 메서드가 없고 insert 메서드가 존재합니다. Set은 중복된 요소가 존재할 수 없기 때문에 이미 Set 안에 존재하는 요소가 들어오면 추가하지 않습니다.
따라서, 이미 들어있는 Six Eggs를 Set에 추가하면 별 다른 에러는 발생하지 않지만, insert에서 반환하는 튜플 중 inserted의 매개변수로 false가 반환됩니다.
print(shoppingList.insert("Six Eggs")) /* (inserted: false, memberAfterInsert: "Six Eggs") */
Swift
복사
아이템을 추가했다면, 삭제도 해봐야겠죠.
배열과 동일하게 아이템 삭제엔 remove메서드를 사용하면 됩니다. 제거할 특정 요소를 넣으면 해당 요소가 제거됩니다. 만약, 해당 요소가 없다면 remove는 nil를 반환하게 됩니다.
shoppingList.remove("Milk")
Swift
복사
Set도 배열과 동일하게 removeAll로 Set 내부에 있는 모든 요소를 지울 수 있습니다. 또한, removeFirst 메서드도 사용할 수 있습니다. 하지만, Set은 순서가 없기 때문에 해당 메서드를 사용했을 때 가장 먼저 들어간 첫 번째 요소를 지우는것이 아니라 무작위 순서에서 첫 번째 요소를 뽑아내서 지웁니다. 그렇기 때문에, 어떤 값이 사라질지 모릅니다.
이제 쇼핑 목록에 있는 물품들을 전체적으로 출력해서 보고 싶습니다.
해당 기능은 for-in 루프를 사용해서 구현할 수 있습니다.
// Basic for element in shoppingList { print(element) } // with enumerated() for (index, element) in shoppingList.enumerated() { print("\(index) \(element)") }
Swift
복사
배열에서는 배열의 요소를 순서대로 출력해줍니다. 하지만, Set은 순서가 없기 때문에 매번 다른 순서로 출력이 됩니다.
만약, 순서대로 출력을 하고 싶다면 어떻게 해야 할까요?
Set이 순서를 가지진 않지만 sorted 메서드를 사용하게 되면, Set을 정렬할 수 있게 됩니다.
for element in shoppingList.sorted() { print(element) } /* Apple Bananas Flour Ice cream Six Eggs */
Swift
복사

Set Element

Set에 Element로 들어가기 위해서는 해당 타입이 hashable을 준수해야 합니다.
왜 hashable을 준수한 타입만 Set에 들어갈 수 있나요?
Set은 해시함수를 이용한 해시값을 저장하고 있는 해시 테이블이라는 자료구조를 사용하고 있습니다.
해시함수는 다양한 길이를 가진 데이터를 고정된 길이를 가진 데이터로 매핑하는 함수입니다. 해당 함수에 동일한 입력값이 오면 동일한 출력을 하게 됩니다. 따라서, Set에서는 인덱스 없이도 해시값을 인덱스처럼 이용해서 빠른 검색을 할 수 있게 됩니다.
그렇기 때문에, Set은 배열에 비해 매우 효율적입니다.
해시를 사용한 데이터 접근 속도가 훨씬 빠르기 때문에 최악의 검색 시나리오 시에도 O(1)O(1)의 시간, 공간 복잡도를 항상 유지합니다.
그렇다면, hashable한 타입은 어떻게 만드나요?
Swift 기본 타입은 모두 hashable을 준수합니다.
그렇기 때문에, 해당 타입들을 Set Value Type이나 Dictionary Key Type으로 사용할 수 있던거죠. 추가적으로, associated value가 아닌 enum case값도 hashable을 준수하기 때문에 Set의 요소 타입으로 사용할 수 있습니다.
만약, 커스텀한 타입을 Set의 Element로 사용하고 싶다면, 해당 타입이 hashable을 준수하도록 추가하기만 하면 됩니다. 그러면 기본 타입처럼 사용할 수 있습니다.

Set Operations

Set은 Set Operation을 가지고 다른 Set과 새로운 Set을 생성할 수 있습니다.
⓵ intersection
두 Set의 교집합인 새로운 Set을 생성합니다.
let oddDigits: Set = [1, 3, 5, 7, 9] let evenDigits: Set = [0, 2, 4, 6, 8] let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] print(oddDigits.intersection(evenDigits).sorted()) print(oddDigits.intersection(singleDigitPrimeNumbers).sorted()) /* [] [3, 5, 7] */
Swift
복사
⓶ symmetricDifference
두 Set의 대칭차집합인 새로운 Set을 생성합니다.
print(oddDigits.symmetricDifference(evenDigits).sorted()) print(oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()) /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 9] */
Swift
복사
⓷ union
두 Set의 합집합인 새로운 Set을 생성합니다.
print(oddDigits.union(evenDigits).sorted()) print(oddDigits.union(singleDigitPrimeNumbers).sorted()) /* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 5, 7, 9] */
Swift
복사
⓸ subtracting
두 Set의 차집합인 새로운 Set을 생성합니다.
print(oddDigits.subtracting(evenDigits).sorted()) print(oddDigits.subtracting(singleDigitPrimeNumbers).sorted()) /* [1, 3, 5, 7, 9] [1, 9] */
Swift
복사
또한, Set 간의 집합 관계를 확인할 수도 있습니다.
⓵ isSubset
해당 Set이 인수로 들어가는 Set의 부분집합인지를 Boolean값으로 반환합니다.
let houseAnimals: Set = ["🐶", "🐱"] let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] print(houseAnimals.isSubset(of: farmAnimals)) /* true */
Swift
복사
⓶ isSuperset
해당 Set이 인수로 들어가는 Set의 초집합인지를 Boolean값으로 반환합니다.
print(farmAnimals.isSuperset(of: houseAnimals)) /* true */
Swift
복사
⓷ isStrictSubset, isStrictSuperset
해당 Set이 인수로 들어가는 Set의 부분집합, 초집합이지만 동일한 집합은 아닌 집합인지를 Boolean값으로 반환합니다.
let houseAnimals: Set = ["🐶", "🐱"] let myPets: Set = ["🐶", "🐱"] print(houseAnimals.isStrictSubset(of: myPets)) /* false */
Swift
복사
⓸ isDisjoint
해당 Set이 인수로 들어가는 Set과 공통된 요소가 없는지를 Boolean값으로 반환합니다.
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] let cityAnimals: Set = ["🐦", "🐭"] print(farmAnimals.isDisjoint(with: cityAnimals)) /* true */
Swift
복사

️ 참고 자료