콘텐츠로 이동

Rust HashMap

해시맵(HashMap)은 데이터를 키-값 쌍으로 저장해요. 배열이나 벡터처럼 위치(인덱스)로 찾는 게 아니라, 키로 값을 찾아요. Python의 딕셔너리나 JavaScript에서 객체를 맵처럼 써 본 적이 있다면, 같은 개념이에요.

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Bob", 25);
println!("{:?}", ages);
}

맨 위의 use std::collections::HashMap;은 이 타입을 스코프로 가져오는 줄이에요. Vec이나 String과 달리 HashMap은 기본으로 쓸 수 있는 타입이 아니어서, 이 줄이 꼭 필요해요.

▶ 직접 실행해 보기
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
match ages.get("Alice") {
Some(age) => println!("Alice는 {age}살이에요"),
None => println!("찾을 수 없어요"),
}
}
▶ 직접 실행해 보기

.get()은 값을 바로 돌려주지 않고 Option을 돌려줘요. 키가 없을 수도 있으니까요. Option은 별도 페이지에서 자세히 다룰 거예요. 지금은 match와 Some/None으로 처리하는 게 표준적인 방법이라고 알아 두세요.

같은 키로 다시 insert하면 이전 값을 덮어써요.

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Alice", 31); // 30을 덮어씀
println!("{:?}", ages.get("Alice"));
}
▶ 직접 실행해 보기

키가 아직 없을 때만 값을 넣고 싶다면 .entry()를 쓰세요.

use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.entry("Alice").or_insert(99); // Alice는 이미 있으니 아무 일도 안 일어남
ages.entry("Bob").or_insert(25); // Bob은 없으니 새로 넣음
println!("{:?}", ages);
}
▶ 직접 실행해 보기
use std::collections::HashMap;
fn main() {
let mut ages = HashMap::new();
ages.insert("Alice", 30);
ages.insert("Bob", 25);
for (name, age) in &ages {
println!("{name}: {age}살");
}
}
▶ 직접 실행해 보기

위치가 아니라 이름이나 ID로 무언가를 찾고 싶을 때는 언제든 HashMap을 떠올리세요. 단어 빈도 세기, 사용자 이름과 사용자 정보 연결하기, 키별로 결과 캐싱하기 같은 경우요. 순서가 중요하거나 그냥 평범한 목록을 다루는 거라면 Vec이 더 잘 맞아요.

HashMap에 이름 세 개와 전화번호를 저장해 보세요. 그다음 "Ben"(전화번호부에 있음)과 "Dana"(없음)를 찾아서, 각각 친절한 문장으로 출력해 보세요. 힌트: ["Ben", "Dana"]를 반복하면서 .get(name)에 match를 쓰세요.

정답 보기
use std::collections::HashMap;
fn main() {
let mut phone_book = HashMap::new();
phone_book.insert("Ana", "555-1234");
phone_book.insert("Ben", "555-9876");
phone_book.insert("Chen", "555-4321");
for name in ["Ben", "Dana"] {
match phone_book.get(name) {
Some(number) => println!("{name}: {number}"),
None => println!("{name}: 전화번호부에 없어요"),
}
}
}

실행 결과:

Ben: 555-9876
Dana: 전화번호부에 없어요
▶ 직접 실행해 보기

맵을 순회하는 대신 찾고 싶은 이름 목록을 순회하면, 출력 순서도 고정돼요. HashMap에서 예측 가능한 출력이 필요할 때 쓸 만한 요령이에요.

투표 결과 ["빨강", "파랑", "빨강", "초록", "파랑", "빨강"]이 있어요. 색깔별로 몇 표를 받았는지 센 다음, "빨강", "초록", "파랑", 그리고 한 표도 못 받은 "분홍"의 합계를 출력해 보세요. 힌트: counts.entry(color).or_insert(0)은 그 색깔의 현재 개수를 0부터 시작해서 돌려줘요. *count += 1로 1을 더하세요.

정답 보기
use std::collections::HashMap;
fn main() {
let votes = ["빨강", "파랑", "빨강", "초록", "파랑", "빨강"];
let mut counts = HashMap::new();
for color in votes {
let count = counts.entry(color).or_insert(0);
*count += 1;
}
for color in ["빨강", "초록", "파랑", "분홍"] {
match counts.get(color) {
Some(n) => println!("{color}: {n}"),
None => println!("{color}: 0"),
}
}
}

실행 결과:

빨강: 3
초록: 1
파랑: 2
분홍: 0
▶ 직접 실행해 보기

or_insert(0)은 맵에 저장된 값의 복사본이 아니라 가변 참조자를 돌려줘요. *count += 1의 *는 “이 참조자가 가리키는 숫자를 바꿔라”라는 뜻이라서, 맵 자체가 갱신돼요. “entry를 찾고, 없으면 0을 넣고, 1을 더한다”는 이 패턴은 Rust에서 무언가를 셀 때 쓰는 표준적인 방법이에요.

  • HashMap<K, V>는 키-값 쌍을 저장해요. use std::collections::HashMap;으로 스코프에 가져와야 해요.
  • .insert(key, value)는 값을 추가하거나 덮어쓰고, .get(key)는 값을 찾아서 Option으로 돌려줘요.
  • .entry(key).or_insert(value)는 키가 아직 없을 때만 값을 넣어요.
  • 순회 순서는 보장되지 않아요. 순서에 의존하지 마세요.

이해도 점검

1. Vec이나 String과 달리, HashMap을 스코프로 가져오려면 무엇이 필요한가요?

2. .entry(key).or_insert(value)는 무엇을 하나요?

3. HashMap을 순회할 때 출력되는 순서를 믿어도 될까요?

점수: 0 / 3