소소한 개발 공부

[Rust] 컬렉션 - 해시맵 본문

프로그래밍/Rust

[Rust] 컬렉션 - 해시맵

이내내 2023. 8. 11. 09:01

해시맵 HashMap<K, V> 은 K 타입의 키, V 타입의 값을 해시 함수를 사용해 매핑한 것을 저장

임의의 타입으로 된 키를 이용해 데이터를 찾고 싶을 때 유용

ex) 각 팀의 점수를 해시맵에 저장했을 때 팀 이름이 키, 점수가 값 -> 팀 이름으로 값 찾기 가능

  

1. 해시맵 생성

new 로 생성, insert로 요소 추가

pub fn insert(&mut self, k: K, v: V) -> Option<V> : K에 해당하는 V가 없다면 None 반환, 있다면 V 반환

use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10); // 키: Blue, 값: 10
scores.insert(String::from("Yellow"), 50);

* 사용량이 적은 컬렉션이라 prelude 의 자동으로 가져오는 기능에 포함되어 있지 않아 use 해야 함

 

2. 해시맵 값 접근

get 메서드에 키를 넣어 값을 가져올 수 있음

let team_name = String::from("Blue");
let score = scores.get(&team_name).copied().unwrap_or(0);

println!("{:?}", score); // 10

get 메서드는 Option<&V>를 반환하는데, 해시맵 키에 해당 하는 값이 없다면 None 반환

copied() -> Option<&i32>가 아닌 Option<i32>를 얻을 수 있게 딥카피해서 iterator 반환

unwrap_or([default]} -> 해당 키에 대한 값을 반환, 없는 경우 defuault 로 0 반환

 

for문도 사용 가능

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

 

3. 해시맵 소유권

insert로 K, V를 넣을 때 move 가 일어날 수 있음

- 참조자를 넣거나 clone 으로 딥카피해서 변수의 소유권을 유지

fn main()
{
    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value); // 여기서 소유권을 잃음

    println!("{} {}", field_name, field_value); // 소유권 문제 발생
}
let field_name = String::from("Favorite color");
   |         ---------- move occurs because `field_name` has type `String`, which does not implement the `Copy` trait
...
9  |     map.insert(field_name, field_value);
   |                ---------- value moved here
10 |
11 |     println!("{} {}", field_name, field_value);
   |                       ^^^^^^^^^^ value borrowed here after move
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
   |
9  |     map.insert(field_name.clone(), field_value);
   |                          ++++++++

-> 쓸거면 clone 해서 insert 하라는 뜻

 

 

4. 해시맵 업데이트

- 값 덮어쓰기

키-값은 유니크함

키에 이미 값이 할당되어 있는 경우 덮어쓴다! (insert 는 키-값 존재 여부 판단 안 함)

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

println!("{:?}", scores); // 이 경우 {"Blue": 25}가 출력됨

 

- 키가 없을 때만 키, 값 추가하기 : entry

entry(매개변수 : 검색하려는 키) -> 반환:열거형 Entry

열거형 Entry : 해당 키 존재 여부 {Occupied, Vacant}

or_insert : Occupied 일 경우 해당 값을 반환 / Vacant일 경우 새 값 삽입 후 해당 값 반환 

pub fn or_insert(self, default: V) -> &'a mut V (가변 참조자 반환)

use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

println!("{:?}", scores); // {"Blue": 10, "Yellow": 50}

 

- 예전 값에 기초해 값 업데이트하기

text 에 각 단어가 몇번 나왔는지 세서 해시맵의 값을 변경시켜주는 예제

* 애스터리스크로 map 의 V에 접근해서 +1 (or_insert가 가변 참조자를 반환해서 역참조로 +1 가능)

let text = "hello world wonderful world";
let mut map = HashMap::new();

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

println!("{:?}", map); // {"wonderful": 1, "hello": 1, "world": 2}

이때 map 의 출력 순서가 다를 수 있는데, 해시맵에 대한 반복 처리가 임의의 순서로 일어나기 때문

 

5. 해시 함수

기본적으로 해시테이블과 관련된 Dos 공격에 저항 기능을 제공하는 SipHash 함수를 사용

가장 빠른 해시 알고리즘은 아니어서 성능은 낮지만 보안을 챙김

- 목적에 따라 다른 hasher를 지정해 해시 함수를 바꿀 수 있음

- hasher: BuildHasher trait을 구현한 타입

- crate.io에 다양한 범용적 해시 알고리즘을 구현한 라이브러리가 있으므로 처음부터 구현하지 않아도 됨