본문 바로가기
iOS

[Swift] Hashable 프로토콜 이해하기

by GODOLs 2023. 11. 17.

목차

    Hashable 프로토콜: Hashable의 중요성

    1. 해시 값의 개념:

    해시 값은 객체를 대표하는 유일한 정수 값입니다. 이 값은 객체의 내용에 기반하여 계산되며, 동일한 객체는 항상 같은 해시 값을 가져야 합니다. 해시 함수는 이러한 해시 값을 생성하는 데 사용됩니다.

    2. 효율적인 데이터 액세스:

    Dictionary의 키나 Set의 원소로 사용될 때, 객체의 해시 값은 해당 객체의 저장 위치를 결정하는 데 사용됩니다. 이로 인해 데이터의 검색, 삽입, 삭제 등의 연산이 매우 빠르게 이루어질 수 있습니다.

    배열(Array)

    • 배열에서의 검색은 선형 검색(linear search)를 사용합니다. 이는 최악의 경우 모든 요소를 확인해야 하므로, 시간 복잡도는 O(n) 입니다. 여기서 n은 배열의 요소 수입니다.
    • 배열에서 특정 값의 존재 여부를 확인하거나, 특정 값을 찾을 때 모든 요소를 순차적으로 확인해야 합니다.

    해시 기반 구조 (Dictionary와 Set)

    • Dictionary와 Set은 내부적으로 해시 테이블을 사용합니다. 이들 구조에서의 요소 검색, 삽입, 삭제 연산의 평균 시간 복잡도는 O(1) 입니다. 즉, 요소의 수와 상관없이 일정한 시간 안에 연산을 완료합니다.
    • 그러나 해시 충돌이 발생하는 경우, 이 시간 복잡도는 최악의 경우 O(n) 까지 증가할 수 있습니다. 이는 해시 테이블에서 동일한 해시 값을 갖는 여러 요소들이 충돌하여, 연쇄적으로 검색 시간이 길어질 수 있기 때문입니다.

    3. 고유한 해시 값의 중요성:

    서로 다른 객체가 같은 해시 값을 가지는 경우를 '해시 충돌'이라고 합니다. 이럴 경우, 컬렉션의 성능이 저하될 수 있기 때문에, 가능한 한 고유한 해시 값을 생성하는 것이 중요합니다.

    4. 예시: Hashable 프로토콜의 구현 및 사용

    struct Book: Hashable {
        var title: String
        var author: String
    
        func hash(into hasher: inout Hasher) {
            hasher.combine(title)
            hasher.combine(author)
        }
    }
    
    var bookCollection: Set<Book> = []
    
    let book1 = Book(title: "1984", author: "George Orwell")
    let book2 = Book(title: "Brave New World", author: "Aldous Huxley")
    
    bookCollection.insert(book1)
    bookCollection.insert(book2)

    이 예시에서, Book 구조체는 Hashable 프로토콜을 준수합니다. hash(into:) 메서드를 통해 titleauthor 프로퍼티의 조합으로 고유한 해시 값을 생성합니다. 이 해시 값은 SetBook 인스턴스를 저장할 때 사용되어, 각 책 객체의 유일성을 보장하고 빠른 접근을 가능하게 합니다.

    Hashable과 Equatable의 관계

    모든 Hashable 타입은 자동으로 Equatable을 준수합니다. (이전에 정리한 내용을 확인해주세요!) 즉, 해시 가능한 객체는 그 객체의 동등성 비교도 가능해야 합니다. 이는 Hashable이 Equatable의 모든 기능을 포함하고, 추가로 해시 값의 생성 기능을 제공한다는 것을 의미합니다.

    실제 예시

    struct Player: Equatable, Hashable {
        var id: Int
        var name: String
    
        static func ==(lhs: Player, rhs: Player) -> Bool {
            return lhs.id == rhs.id
        }
    
        func hash(into hasher: inout Hasher) {
            hasher.combine(id)
        }
    }
    
    // 사용 예
    let player1 = Player(id: 1, name: "Alice")
    let player2 = Player(id: 2, name: "Bob")
    
    // Equatable을 사용한 비교
    print(player1 == player2) // false
    
    // Hashable을 사용한 Set 저장
    var playersSet: Set<Player> = []
    playersSet.insert(player1)
    playersSet.insert(player2)

     

    이 예시에서 Player 구조체는 두 프로토콜을 모두 준수하며, 이를 통해 객체의 동등성 비교와 해시 컬렉션에서의 사용이 가능합니다.

    결론

    Hashable과 Equatable 프로토콜은 Swift에서 객체의 동등성 비교와 데이터 관리를 위해 필수적입니다. 이 두 프로토콜을 적절히 활용함으로써, 사용자 정의 타입의 효율적인 관리 및 처리가 가능해집니다. Swift의 타입 시스템과 컬렉션 관리 기능을 최대한 활용하기 위해서는 이들 프로토콜의 이해와 적용이 매우 중요합니다.

    반응형