해쉬맵 (HashMap)

HashMap 생성

HashMap은 컬렉션의 일종으로, HashMap<K, V> 타입은 K 타입의 키에 V 타입의 값을 매핑한 것을 저장합니다. 해쉬맵은 벡터를 이용하듯 인덱스를 이용하는 것이 아니라 임의의 타입으로 된 키를 이용하여 데이터를 찾기를 원할때 유용합니다.


1. Struct와 HashMap

항목StructHashMap
정의 방식컴파일 타임에 고정된 필드와 타입 정의런타임에 키-값 쌍으로 유동적으로 데이터 저장
고정된 필드 이름임의의 키 (보통 String 등 Eq + Hash 트레잇 필요)
타입 안정성필드 타입이 컴파일 시점에 결정됨모든 값이 동일 타입이거나 제네릭으로 지정됨
사용 목적고정된 구조의 데이터 표현동적으로 키/값 데이터를 저장하고 조회

※ 제네릭(Generic) : 어떤 자료형이든 사용할 수 있도록 유연하고 재사용 가능한 코드를 만드는 기능

2. HashMap 생성과 삽입

HashMap을 사용하려면 먼저 std::collections에서 가져와야 합니다.

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert("Blue", 10);
    scores.insert("Red", 50);
}

HashMap::new()로 HashMap을 생성합니다.

let mut scores = HashMap::new();
는 가변형 scores란 변수에 빈 HashMap을 대입하는 것입니다. 다시 말하면 빈 HashMap을 가변형 scroes란 이름으로 만드는 것입니다.

insert(key, value)로 값을 추가할 수 있습니다.

scores.insert(“Blue”, 10);
는 scores는 HashMap의 키로 Blue(&str type), 값으로 10을 대입하는 것이고,

scores.insert(“Red”, 50);
는 scores는 HashMap의 키로 Red(&str type), 값으로 50을 대입하는 것입니다.

이때 HashMap은 키와 값의 타입을 모두 제네릭으로 받으며, 보통 자동 추론됩니다.
Blue와 Red는 &str 형식이고, 10과 50은 정수이므로 i32로 해쉬맵의 형식이 추론된 것입니다.

“Blue”와 “Red”를 String::from(“Blue”)와 String::from(“Red”)로 수정하면, Key의 type이 String으로 자동 변경됩니다.

첫번째는 String type이고, 두번째는 &str 타입이면 두번째 key 아래에 빨간색 물결 줄이 표시되는데, 커서를 갖다대면 mismatched types 에러가 표시됩니다.


3. 값 접근 및 소유권

값을 읽을 때는 get() 메서드를 사용합니다.

let team_name = "Blue";
let score = scores.get(team_name);
println!("{:?}", score); // Some(10)

get()은 Option<&V>를 반환하므로, Some(v) 또는 None으로 패턴 매칭하거나 unwrap_or 등을 활용해야 합니다.

    match score {
        Some(score) => println!("{team_name} 팀의 점수: {score}"),
        None => println!("{team_name} 팀의 점수를 찾을 수 없습니다."),
    }

println!(“{:?}”, score); 의 결과는 Some(10)인데, 위와 같이 match 흐름 제어 연산자를 사용하면 점수가 있으면 점수, 여기서는 10이 반환되고,

팀 이름을 이름이 없는 Green으로 변경하면 Option 값은 None이 출력되고, match 연산자의 결과는 “Green 팀의 점수를 찾을 수 없습니다.”란 메시지가 나오게 됩니다.

소유권 이슈

Rust의 HashMap은 키와 값의 소유권을 가져갑니다.

let field = String::from("Favorite color");
let value = String::from("Blue");

let mut map = HashMap::new();
map.insert(field, value);
// println!("{}", field); // 오류! 소유권이 이동됨

따라서, 위 코드에서 println! 앞의 주석을 제거하고 실행하면
value borrowed here after move 에러가 발생합니다.

위 오류를 없애려면 map.insert안 의 field를 &field로 수정하면 됩니다.


4. for 루프로 순회하기

HashMap의 모든 값을 순회하려면 for 루프를 사용합니다.

for (key, value) in &scores {
    println!("{}: {}", key, value);
}

참조로 순회하므로 키나 값을 수정하진 않지만, 읽을 수 있습니다.


5. 조건부 삽입: entry()와 or_insert()

entry()는 키의 존재여부에 따라 다른 동작을 하게 해주는 API입니다. entry 함수의 리턴값은 열거형 Entry인데, 해당 키가 있는지 혹은 없는지를 나타냅니다.

scores.entry("Yellow").or_insert(30);
scores.entry("Blue").or_insert(50);
  • “Yellow”라는 key가 없기 때문에 value 30이 추가되고,
  • “Blue”는 이미 존재하므로 기존 값(10)이 유지됩니다.

or_insert()는 존재하지 않을 때만 값을 삽입하는 매우 유용한 메소드입니다.


6. 값을 업데이트하기

HashMap의 값을 직접 변경하려면 get_mut() 또는 entry()를 활용할 수 있습니다.

기존 값 수정 (1) :

if let Some(score) = scores.get_mut("Red") {
    *score += 10;
}

if let Some(score) = scores.get_mut(“Red”) {
: score가 Red의 score라면 score를 가변 참조로 반환하는데,

*score += 10;
: 참조를 반환하므로 값을 수정하기 위해서는 dereference(역참조, 포인터가 가리키는 메모리 주소에 저장된 값을 읽거나 쓰는 것)하는 연산자 *를 앞에 붙여야 하며, 이 구문은 score에 10을 더하는 것입니다.

기존 값 수정 (2) :

또는 entry() 기반으로도 가능:

scores.entry("Red").and_modify(|v| *v += 1);

Red라는 키가 있으면, value를 v로 받아서, 1을 더하는 것입니다.


7. 예제: 단어 개수 세기

HashMap은 문자열 데이터를 처리할 때 특히 유용합니다. 예를 들어, 문장에서 각 단어의 개수를 세어보겠습니다.

이 코드는 split_whitespace()로 공백 기준으로 단어를 나누고, 각 단어가 등장한 횟수를 계산합니다.

use std::collections::HashMap;

fn main() {
    let text = "hello world hello rust";

    let mut counts = HashMap::new();

    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }

    println!("{:?}", counts);
}

*counts.entry(word).or_insert(0) += 1;
: word란 키가 있으면 value에 1을 더하고, 없으면 value에 0을 추가하는 것입니다.

출력 결과는 아래와 같은데
{“hello”: 2, “world”: 1, “rust”: 1}

key 순으로 정렬하지 않아 표시되는 순서가 매번 달라집니다.

9. HashMap 요약

  • HashMap는 키-값 쌍을 저장하는 자료구조
  • insert()로 값 추가, get()으로 값 조회
  • 키는 고유해야 하며, 같은 키로 다시 삽입하면 덮어씀
  • entry()와 or_insert()로 조건부 삽입 가능
  • 순회는 for (k, v) in &map
  • 소유권은 HashMap으로 이동됨
  • 문자열 처리에 매우 유용함

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다