HashMap

개요

HashMap<K,V>는 해시 테이블 기반 key-value 저장소로, 평균 O(1) 삽입/조회를 제공합니다. Separate chaining으로 충돌을 처리하며, 제네릭 키 타입을 지원합니다. 문자열 키 전용 최적화 버전으로 StringMap도 제공됩니다.

Quick Start

U std/hashmap

F main() -> i64 {
    m := HashMap::new()
    m.set(1, 100)
    m.set(2, 200)
    val := m.get(1)  # 100
    R 0
}

API 요약

함수설명시간 복잡도
HashMap::new()빈 HashMap 생성 (capacity=16)O(1)
HashMap::with_capacity(n)지정 capacity로 생성O(n)
set(key, val)키-값 삽입/업데이트O(1) 평균
get(key)키로 값 조회O(1) 평균
contains(key)키 존재 여부O(1) 평균
remove(key)키-값 쌍 제거O(1) 평균
len()요소 개수O(1)
is_empty()빈 맵 여부O(1)
clear()모든 요소 제거O(n)

실용 예제

예제 1: 기본 사용법

U std/hashmap

F main() -> i64 {
    scores := HashMap::new()

    # 삽입
    scores.set(100, 85)  # ID 100 학생의 점수 85
    scores.set(101, 92)
    scores.set(102, 78)

    # 조회
    I scores.contains(101) {
        score := scores.get(101)
        print_i64(score)  # 92
    }

    # 제거
    scores.remove(102)
    R 0
}

예제 2: 빈도 카운팅

U std/hashmap
U std/vec

F count_frequencies(numbers: Vec<i64>) -> HashMap<i64,i64> {
    freq := HashMap::new()
    i := 0
    L i < numbers.len() {
        num := numbers.get(i)
        count := I freq.contains(num) { freq.get(num) } E { 0 }
        freq.set(num, count + 1)
        i = i + 1
    }
    R freq
}

F main() -> i64 {
    v := Vec::new()
    v.push(1)
    v.push(2)
    v.push(1)
    v.push(3)
    v.push(2)
    v.push(1)

    freq := count_frequencies(v)
    print_i64(freq.get(1))  # 3 (1이 3번 등장)
    R 0
}

예제 3: 캐시 구현

U std/hashmap

S Cache<K,V> {
    map: HashMap<K,V>,
    max_size: i64
}

X Cache<K,V> {
    F new(max_size: i64) -> Cache<K,V> {
        Cache {
            map: HashMap::with_capacity(max_size),
            max_size: max_size
        }
    }

    F get_or_compute(&self, key: K, compute_fn: F(K) -> V) -> V {
        I self.map.contains(key) {
            R self.map.get(key)
        }

        # 캐시 미스 - 계산 후 저장
        value := compute_fn(key)
        I self.map.len() < self.max_size {
            self.map.set(key, value)
        }
        R value
    }
}

예제 4: StringMap 사용 (문자열 키 최적화)

U std/stringmap

F main() -> i64 {
    config := StringMap::new()

    # 문자열 키-값 저장
    config.set("host", "127.0.0.1")
    config.set("port", "8080")
    config.set("debug", "true")

    # 조회
    host := config.get("host")
    I host != 0 {
        print_str(host)  # "127.0.0.1"
    }

    R 0
}

예제 5: 해시 충돌 처리 확인

U std/hashmap

F main() -> i64 {
    m := HashMap::with_capacity(4)  # 작은 capacity로 충돌 유도

    # 많은 키 삽입 (자동 rehash)
    i := 0
    L i < 100 {
        m.set(i, i * 2)
        i = i + 1
    }

    # 모두 정상 조회 가능
    print_i64(m.get(50))  # 100
    print_i64(m.len())    # 100
    R 0
}

주의사항

1. 해시 함수 선택

현재 구현은 mult_hash(key) 함수를 사용합니다. 커스텀 타입을 키로 사용하려면 해시 함수를 구현해야 합니다.

# 커스텀 해시 필요
S CustomKey { id: i64, name: i64 }

F hash_custom(k: CustomKey) -> i64 {
    R k.id * 31 + hash_str(k.name)
}

2. Rehashing 오버헤드

load factor(len/capacity)가 0.75를 초과하면 자동 rehash가 발생합니다. 대량 삽입 전에 with_capacity()로 충분한 용량을 확보하세요.

# 나쁜 예: 여러 번 rehash
m := HashMap::new()  # capacity=16
L i < 10000 { m.set(i, i); i = i + 1 }  # ~log(n) rehash

# 좋은 예: rehash 0~1회
m := HashMap::with_capacity(15000)  # 10000 / 0.75 ≈ 13333
L i < 10000 { m.set(i, i); i = i + 1 }

3. 메모리 누수

제거된 Entry 노드는 명시적으로 free()되어야 합니다. 현재 clear()는 bucket 포인터만 초기화하므로, 긴 생명주기 HashMap은 누수 위험이 있습니다.

4. StringMap vs HashMap

문자열 키만 사용한다면 StringMap이 더 효율적입니다. 내부적으로 문자열 전용 해시 함수를 사용하여 성능이 향상됩니다.

# 문자열 키 → StringMap 사용
config := StringMap::new()
config.set("key", "value")

# 정수 키 → HashMap 사용
scores := HashMap::new()
scores.set(123, 456)

5. 반복 순서 미보장

HashMap은 해시 순서로 저장되므로, 삽입 순서와 무관합니다. 순서가 중요하면 BTreeMap을 사용하세요.

6. 동시성 주의

HashMap은 thread-safe하지 않습니다. 멀티스레드 환경에서는 Mutex<HashMap<K,V>>로 래핑하세요.

U std/sync
U std/hashmap

global_cache := Mutex::new(HashMap::new())

F thread_safe_insert(key: i64, val: i64) -> i64 {
    guard := global_cache.lock()
    cache := guard.get_inner()
    cache.set(key, val)
    R 0
}

See Also