상세 컨텐츠

본문 제목

[Day7] Collective Types (2) : Set

Swift&SwiftUI

by (방울)도마토 2024. 4. 15. 21:26

본문

Collective Types (집단 자료형)

1. Array : 일련번호로 구분되는 순서에 따라 데이터가 정렬된 목록 형태의 자료형

2. Set : 중복되지 않은 유일 데이터들이 모인 집합 형태의 자료형 

3. Tuple : 종류에 상관없이 데이터들을 모은 자료형, 수정 및 삭제가 불가능함

4. Dictionary : key를 사용하여 key-value로 연관된 데이터들이 순서 없이 모인 자료형 

 

 

Set

- 같은 타입의 서로 다른 값을 중복 없이 저장하고자 할 때 사용하는 집단 자료형 

- 순서가 그다지 중요하지 않은 데이터 

- 중복 없이 한 번만 저장되어야 하는 데이터들을 다룰 때 사용 


Hash 연산의 결과값 이용

- 해쉬 연산?

    - Hash Algorithm, 임의의 입력된 메시지를 고정 길이의 데이터 크기로 변환해주는 알고리즘 

        - 아무리 긴 데이터나 아무리 짧은 길이의 데이터도 고정 길이의 데이터로 변환할 수 있음 

    ex. % (나머지 연산자)

        - 아무리 큰 값의 수라 할지라도 10으로 나눈 나머지를 구하면 0~9까지 중에서 한자리 길이의 값으로 변환 가능 

    - 해시 충돌 : 서로 다른 값을 연산했을 때 같은 결과값이 나올 가능성 

        - 해시 충돌의 가능성이 낮을 수록 좋은 알고리즘 

    - 해시 알고리즘 : MD5, SHA1, SHA256

    - 입력값이 변경될 경우 해시 연산의 결과값도 달라짐 

        - 데이터의 무결성 검증이나 메시지 인증 등에 사용, 해시 연산값을 기준으로 데이터를 정렬하여 저장하는 데 사용(딕셔너리)

    - 해시 연산값을 이용한 자료 탐색 → 현존하는 자료 탐색 기술 중 가장 빠른 기술 

    - 연산 처리 과정에서 일정 부분 원본 데이터의 손실이 발생, 해시 연산으로 얻은 결과값을 역이용하여 본래의 데이터를 구하는 것은 수학적으로 매우 어려운 일 → 복호화가 필요 없는 암호화에 많이 사용 

 

- 집합은 내부적으로 Hash 연산의 결과값을 이용하여 데이터를 저장 

    - 집합에 저장할 데이터 타입은 해시 연산을 할 수 있는 타입이어야 함 

    - 집합에 저장할 데이터 타입은 반드시 해시값을 계산하는 방법을 제공해야 함 

    - 임의로 만든 타입을 사용하여 집합의 아이템으로 저장하려면, Swift 표준 라이브러리에서 제공하는 Hashable 프로토콜을 구현해야 함 


집합의 정의

1. 초기값을 사용하여 집합을 정의

// 아이템의 타입 추론이 가능할 때 
var genres : Set = ["Classic", "Rock", "Balad"]
// 저장할 아이템의 타입을 명시해주는 경우 : 원칙
var genres : Set<String> = ["Classic", "Rock", "Balad"]
// 텅 빈 배열을 사용하여 집합 저장 
var g : Set<String> = []

- 집합을 정의할 때는 배열 데이터를 사용하여 정의 

    - 집합 타입임을 컴파일러에 알려주기 위해 Type Annotation Set을 기재해야 함 

- 텅 빈 배열을 사용하여 집합을 정의할 경우 아이템 타입을 반드시 작성해주어야 함 

 

2. 빈 집합을 선언하고 초기화하는 과정

Set <아이템 타입> ()

var genres = Set<String>()

// 집합에 아이템 추가 : insert(_:)
genres.insert("Classic")
genres.insert("Rock")
genres.insert("Balad")

 

 

- 작성된 집합은 count를 통해 크기 확인 가능 

    - 배열은 중복된 데이터를 허용하는 반면, 집합은 중복 데이터를 허용하지 않으므로 같은 데이터를 저장하였다 하더라도 배열과 집합은 크기가 다를 수 있음 

- isEmpty 속성을 사용하면 보다 명확하게 빈 집합인지 판단 가능 

// 집합이 비었을 때는 count -> 0, isEmpty -> true
// Read-Only 속성이므로 우리가 임의로 설정 불가능 
if genres.isEmpty {
    print("집합이 비었습니다")
} else {
    print("저장 개수 : \(genres.count)")
} // 저장 개수 : 3

 

 

집합 순회 탐색 

- 배열처럼 인덱스를 활용할 수 없으나 순회 속성을 활용하여 집합 자체를 for~in 구문에 그대로 넣고 순회 처리하여 탐색할 수 있음 

var genres : Set = ["Classic", "Rock", "Balad"]

// 순회 처리하면서 아이템 출력 
for g in genres {
    print("\(g)")
} // Balad
// Rock
// Classic

// sorted() : 정렬된 결과, 집합 자체에 순서 적용 X, 단순히 메소드 반환값 정렬 
for g in genres.sorted() {
    print("\(g)")
} // Balad
// Rock
// Classic

 

 

집합의 동적 추가와 삭제

- 추가 : insert(_:)

    - 함께 전달된 인자값을 집합에 추가, 이미 같은 아이템이 저장되어 있을 때는 아무 처리도 하지 않음 

var genres : Set = ["Classic", "Rock", "Balad"]

genres.insert("Jazz")
// genres = ["Jazz", "Classic", "Rock", "Balad"]
genres.insert("Rock")
// genres = ["Jazz", "Classic", "Rock", "Balad"]
    // Rock을 여러 번 반복하여 추가하여도 더는 추가되지 않음

 

- 삭제 : remove(_:)

    - 메소드가 호출되면 집합의 내부 저장소를 검색하여 입력하된 인자와 일치하는 아이템을 찾고, 있으면 아이템을 삭제하고 삭제된 값을 반환 

    - 삭제할 값이 없으면 remove(_:) 메소드는 아무것도 삭제하지 않고 nil을 반환 

if let removedItem = gernes.remove("Rock") {
    print("Removed Item : \(removedItem)")
} else {
    print("삭제할 값이 집합 내부에 존재하지 않습니다")
} // Removed Item : Rock

- 집합에 추가되지 않은 값을 삭제하려면 했다면 nil이 반환되어 else 구문이 실행 

 

- 아이템 전체 삭제 : removeAll(_:)

    - 인자값 없이 호출되어 해당 집합의 모든 아이템 일괄 삭제 

genres.removeAll() // genres의 모든 아이템 삭제 

if genres.isEmpty {
    print("모든 아이템이 삭제되었습니다")
} else {
    print("남은 아이템의 개수 : \(genres.count)")
} // 모든 아이템이 삭제되었습니다

 

- 특정 아이템 여부 확인 : contains(_:)

    - 인자값으로 입력된 데이터를 사용하여 해당 집합 내에 일치하는 아이템이 있는지 검색 

var genres : Set = ["Classic", "Rock", "Balad"]

if genres.contains("Rock") {
    print("Rock = true")
} else {
    print("Rock = false")
}

 

 

 

집합 연산

- 집합 자료형끼리는 집합 연산을 할 수 있음 

1. intersection(_:) : 교집합 

2. symmetricDifference(_:) 

- 양쪽 집합 중 어느 한쪽에만 있는 아이템을 선택하여 새로운 집합을 생성, 양쪽 집합 모두에 공통으로 있는 아이템은 제외 

3. union(_:) : 합집합 

4. subtract(_:) : 차집합 

 

- subtract(_:)을 제외하면 나머지 메소드는 양쪽 집합 위치가 바뀌더라도 결과값은 동일 

var oddDigits : Set = [1, 3, 5, 7, 9]
let evenDigits : Set = [0, 2, 4, 6, 8]
let primeDigits : Set = [2, 3, 5, 7]

oddDigits.intersection(evenDigits).sorted() // 교집합, []
oddDigits.symmetricDifference(primeDigits).sorted() // 교집합 제외, [1, 2, 9]
oddDigits.union(evenDigits).sorted() 
    // 합집합, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
oddDigits.subtract(primeDigits)
oddDigits.sorted() // [1, 9]

- subtract(_:)의 경우 세 메소드와 달리 실행 결과로 새로운 집합을 만들지 않음 

    - 대상 집합에서 아이템을 직접 제거함 → 대상 집합의 내용을 직접 변경!

    - 따라서 subtract(_:)을 사용하는 경우 대상이 되는 집합은 반드시 변수로 선언 

- subtract(_:)은 아무 값도 반환하지 않으므로 sorted()를 바로 이어서 호출할 수 없음 

 

 

부분집합과 포함관계 판단 연산

- isSubset(of:)

    - 하나의 집합이 다른 집합의 부분집합인지 여부 판단

- isSuperset(of:)

    - isSubset(of:) 과 반대 상황 판단 

    - 주어진 집합이 특정 집합의 모든 값을 포함하는지 판단 (집합이 다른 집합의 상위집합 역할을 하는가)

- isStrictSubset(of:), isStrictSuperset(of:)

    - 주어진 집합이 특정 집합의 부분집합인지 상위집합인지 판단

         - 두 집합이 서로 같은 경우 결과값이 다르게 반환 

    ex. 두 집합이 서로 일치할 경우 수학적으로는 서로가 서로의 부분집합이자 상위집합이 될 수 있음 

         - isSubset(of:), isSuperset(of:) → true 반환 

         - isStrictSubset(of:), isStrictSuperset(of:) → 더 엄격하게 판단하여 정확히 부분집합, 상위집합일 때만 true 반환 

- isDisjoint(with:)

    - 아무런 공통 값이 없을 때 true, 공통 값이 하나라도 있을 경우 false

let A : Set = [1, 3, 5, 7, 9]
let B : Set = [3, 5]
let C : Set = [3, 5]
let D : Set = [2, 4, 6]


B.isSubset(of: A) // true
A.isSuperset(of: B) // ture
C.isStrictSubset(of: A) // true
C.isStrictSubset(of: B) // false : 동일한 집합이나 부분집합은 아님
A.isDisjoint(with: D) // ture

 

 

집합과 배열의 차이점

- 순서, 인덱스가 없고 중복된 아이템을 허용하지 않음 

    - 특수한 경우 배열을 대신하기 위한 용도로 사용 

    ex. 중복된 값이 있으면 안 되는 데이터 모음을 정의

    - 값이 중복으로 등록되거나 배열에서 중복된 값을 걸러내고 싶을 때 → 집합을 사용하면 손쉽게 중복 값 제거 가능 

var A = [4, 2, 5, 1, 7, 4, 9, 11, 3, 5, 4]

// A = Array(Set(A))
let B = Set(A)
A = Array(B) // 중복이 제거된 배열

// [2, 4, 9, 5, 7, 3, 1, 11]

 

관련글 더보기

댓글 영역