해시맵



모두에게 익숙한 그 해시맵 타입을 알아보기로 함. HashMap<K, V>(이하 해시맵) 은 std 라이브러리에서 제공하지만, 벡터만큼 사용 빈도가 높다고 여겨지진 않았는지 자동으로 로드되지 않음. use 를 사용해야 함. 해시맵은 제너릭 타입이 적용되며, 요소의 키와 값이 각각 같아야 함.



해시맵 생성


기본적으로 아래와 같이 생성하고 데이터를 넣음.


use std::collections::HashMap;

let mut scores = HashMap::new();

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


collect 메서드를 사용하면 두 벡터를 이용해서 맵으로 합칠 수 있음.


use std::collections::HashMap;

let teams = vec![String::from("Blud"), String::from("Yellow")];
let initial_scores = vec![10, 50];

let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();

teams 의 요소는 키로, initial_scores 의 요소는 값으로 1:1 매핑된 해시맵이 생성됨.


타입을 '<_, _>' 으로 지정했는데, 이는 collect 메서드는 다양한 타입의 맵을 리턴할 수 있고, 호출 결과로 어떤 타입이 올지 모르기 때문. '_' 를 사용하면 러스트는 벡터 안의 데이터 타입을 기반으로 해시맵의 타입을 추론할 수 있음.



해시맵과 오너십


해시맵의 키/값 insert 에도 오너십 규칙이 적용됨.


let key = String::from("color");
let value = String::from("blud");

let mut map = HashMap::new();
map.insert(key, value); // key, value 의 오너십 이동

변수 key, value 는 오너십을 상실함.


어떤 값의 레퍼런스(참조)를 해시맵에 넣는다면, 그 값은 해시맵으로 이동하지 않음. 레퍼런스가 가리키는 값은 해시맵이 유효한 이상 반드시 유효해야 함. 이는 라이프타임에 관한 것인데, 후에 자세히 다룸.



해시맵 값 접근


아래와 같이 get 메서드를 이용해서 해시맵에 저장된 값에 접근할 수 있음.


use std::collections::HashMap;

let key = String::from("color");
let value = String::from("blue");

let mut map = HashMap::new();
map.insert(key, value);

let blue = map.get(String::from("color"));


get 메서드는 Option<&V> 를 리턴함. 위의 변수 blue 는 Some(&String::from("color")) 가 됨. 만약 키에 해당하는 값이 없으면 get 메서드는 None 을 리턴함.


아래의 방법으로 맵을 순회하며 키와 값을 얻을 수 있음.


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

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



해시맵 값 변경


해시맵은 하나의 키로 하나의 값만 할당할 수 있음. 만약 맵에 넣으려는 키에 해당하는 값이 이미 존재할 경우, 새로운 값으로 덮어쓸 것인지 기존 값을 유지할 것인지 결정해야 함.



값 덮어쓰기


아래와 같이 이미 존재하는 키로 값을 넣으면 그냥 새로운 값으로 덮어쓰고 기존 값은 사라짐.


map.insert(String::from("Blue"), 10);
map.insert(String::from("Blue"), 50);



값 유지하기


해시맵에 entry 메서드가 있음. 파라미터로 키 값을 받아서, 이 키에 해당하는 값이 이미 있는지 체크하고 그 결과를 리턴함. 리턴 타입은 enum 타입 Entry 임. 아래와 같이 사용.


map.insert(String::from("Blue"), 10);
map.entry(String::from("Yellow")).or_insert(50);
map.entry(String::from("Blue")).or_insert(50);


Entry 타입의 메서드 or_insert 는 Entry 의 값이 있을 경우 그 값을 리턴하고, 값이 없을 경우 파라미터로 주어진 값을 맵에 넣음. 위의 코드에서는 키 Yellow 는 값이 없으니 50 을 값으로 넣고, 키 Blue 는 값이 있으니 기존 값 10을 유지함.



기존 값을 기반으로 값 변경하기


기존 값을 그대로 덮어쓰거나 유지하지 않고, 기존 값을 이용해서 새로운 값을 만드는 경우도 있음.


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, "world": 2, "hello": 1}


코드의 수행 과정을 다음과 같음.


1. for word in text.split_whitespace() {..} 는 text 의 문자열을 공백 기준으로 잘라낸 결과를 가지고 순회.


2. let count = map.entry(word).or_insert(0); 는 map 에 해당 단어가 키로 존재하는지 체크(entry)하여 or_inert(0) 호출, 즉 값이 이미 있다면 있는 값을 리턴하고, 없다면 0 을 값으로 맵에 넣음.


3. *count += 1; 은 존재하는 값에 1 을 더함.


결국 이미 키가 존재하는 경우 count 를 1 더하고, 없으면 0 으로 초기화 함. or_insert 메서드는 값이 있는 경우 뮤터블 레퍼런스(*mut V) 를 리턴하기 때문에, 역참조(*) 로 값을 변경할 수 있음.



해싱 함수


기본적으로 러스트의 해시맵은 서비스 거부 공격(Denial of Service)을 방어할 수 있는, 암호화된 보안 해싱 함수를 사용함. 이 함수는 가장 빠른 해싱 함수는 아님. 성능을 어느 정도 희생하여 안정성을 취한 형태. 만약 다른 해싱 함수를 사용하길 원한다면, hasher 를 명시하여 원하는 해싱 함수를 지정할 수 있음. 이에 대한 자세한 내용은 나중에 다루기로 함.




'Rust' 카테고리의 다른 글

에러 핸들링(Result)  (0) 2018.02.26
에러 핸들링(panic)  (0) 2018.02.26
컬렉션(스트링)  (0) 2018.02.24
컬렉션(벡터)  (0) 2018.02.24
모듈(다른 모듈 접근)  (0) 2018.02.22

+ Recent posts