C++: 맵(Map)/세트(Set)
C++: 맵(Map)/세트(Set)
맵(Map)
- Key와 그에 대응되는 value, 데이터 쌍의 집합으로 이루어진 컨테이너다.
- 내부 작동은 std::pair를 자동 정렬 이진트리의 한 종류인 red-black tree에 넣어놓은 구조로, Key값의 크고 작음에 따라 자동으로 정렬된다.
- 여러 기본적인 사용법이 존재한다.
- map[key]: key값에 해당하는 value를 반환한다. 없으면 기본값인 0을 반환한다. 배열의 인덱스 접근자처럼 값의 수정도 가능하다.
- 단순 탐색이 목적이고 map을 변형시키면 안된다면, 이 방법은 [key, 0]라는 쌍을 새롭게 추가해 길이를 늘리게 되니 조심하자. 그때는 count를 사용하는게 더 좋다.
- 근데 카운팅이 목적이라면 map[n]++; 처럼 사용할 때 별도의 코드 없이 map으로 카운팅 할수있어 오히려 편리하다.
- insert({key, value}): key, value 쌍을 추가한다.
- erase(key): key에 해당하는 쌍을 map에서 제거한다.
- find(key): key에 해당하는 위치의 반복자를 반환한다.
- count(key): key가 있으면 1, 없으면 0을 반환한다.
- clear(): 내용물을 모두 없애 비운다.
- map[key]: key값에 해당하는 value를 반환한다. 없으면 기본값인 0을 반환한다. 배열의 인덱스 접근자처럼 값의 수정도 가능하다.
- Key와 그에 대응되는 value, 데이터 쌍의 집합으로 이루어진 컨테이너다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <string>
#include <map>
#include <iostream>
using namespace std;
int main()
{
//map<key자료형, value 자료형>, 선언과 동시에 초기화도 가능
map<string, int> HR = {{"CEO", 1}};
//[]를 이용한 값 추가, 뒤죽박죽 넣어도 순서대로 정렬된다.
HR["IT"] = 3;
HR["Cook"] = 5;
//반복문을 이용한 출력
for (auto Resources : HR) {
cout << Resources.first << ": " << Resources.second << endl;
}
cout << endl;
//insert를 사용한 추가
HR.insert(pair <string, int>("Security", 3));
HR.insert({"Inbound", 2});
//반복자를 이용한 출력
for (auto i = HR.begin(); i != HR.end(); i++) {
cout << i->first << ": " << i->second << endl;
}
cout << endl;
//erase를 이용한 삭제
HR.erase("IT");
//반복문을 이용한 출력
for (auto Resources : HR) {
cout << Resources.first << ": " << Resources.second << endl;
}
cout << endl;
//find를 이용한 검색
auto temp1 = HR.find("IT"); //IT 존재하지 않음
if (temp1 != HR.end()) {
cout << temp1->first << ": " << temp1->second << endl;
}
else {
cout << "Not Found!" << endl;
}
temp1 = HR.find("Cook"); //Cook 검색 결과 출력
if (temp1 != HR.end()) {
cout << temp1->first << ": " << temp1->second << endl;
}
else {
cout << "Not Found!" << endl;
}
cout << endl;
//count를 이용한 존재여부 확인
cout << "Inbound: " << HR.count("Inbound") << endl;
cout << "CS: " << HR.count("CS") << endl;
cout << endl;
//없는 키값 []호출시 size증가
cout << "size: " << HR.size() << endl;
cout << "CS: " << HR["CS"] << endl;
cout << "size: " << HR.size() << endl;
//clear를 통해 비울 수 있음
HR.clear();
for (auto Resources : HR) { //출력할 값 존재하지 않음.
cout << Resources.first << ": " << Resources.second << endl;
}
}
- key와 value는 모든 자료형을 쓸 수 있다. 다만 key값에 사용자가 정의한 클래스나 비교 연산자가 기본정의되있지 않은 자료형을 사용하고자 한다면, 비교연산자 ‘<’를 정의 해주어야한다. 어떤 값을 바탕으로 내부 트리를 정렬해야할 지 알려줘야 하기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string>
#include <map>
#include <iostream>
using namespace std;
class A {
public:
int num;
A(int val) : num(val) {
}
bool operator < (const A& other) const {//비교연산자 정의
return num < other.num;
}
};
int main()
{
//map<key자료형, value 자료형>
map<A, int> Map;
//뒤죽박죽 넣어도 3 5 13 50으로 정리
Map[A(13)] = 1;
Map[A(5)] = 1;
Map[A(3)] = 1;
Map[A(50)] = 1;
for (auto x : Map) cout << x.first.num << ": " << x.second << endl;
}
셋(Set)
- map에서 값이 존재하지 않는, 키값만으로 이루어진 컨테이너이다.
- 키에 대응되는 별도의 값이 존재하지 않기에, 같은 키값이 주어지면 중복으로 추가되지 않는다.
- 더 자세히 들어가면 그냥 이진 트리, 그중에서도 Red-Black tree와 같다. 때문에 값이 추가되고 삭제되는 것에 따라 자동으로 정렬된다.
- insert, count, erase등 기본적인 사용은 map과 유사하다. 위에 언급한 모든 함수들은 set에서도 작동한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
#include <string> #include <set> #include <iostream> using namespace std; int main() { //set<자료형> 이름, 선언과 함께 초기화 가능 set<string> Items = {"Potion", "Sword", "Apple"}; //반복문을 이용한 출력 for (auto item : Items) { cout << item << endl; } cout << endl; //insert를 사용한 추가 Items.insert("Bow"); Items.insert({ "Inbound", "Crossbow"}); //배열로 여러개 추가도 가능 //반복자를 이용한 출력 for (auto i = Items.begin(); i != Items.end(); i++) { cout << *i << endl; } cout << endl; //erase를 이용한 삭제 Items.erase("Sword"); for (auto item : Items) { cout << item << endl; } cout << endl; //find를 이용한 검색 auto temp = Items.find("Armor"); //Armor 존재하지 않음 if (temp != Items.end()) { cout << *temp << endl; } else { cout << "Not Found!" << endl; } temp = Items.find("Apple"); //Apple 검색 결과 출력 if (temp != Items.end()) { cout << *temp << endl; } else { cout << "Not Found!" << endl; } cout << endl; //count를 이용한 검색 cout << "Potion: " << Items.count("Potion") << endl; cout << "Boots: " << Items.count("Boots") << endl; cout << endl; //clear를 통한 초기화 Items.clear(); for (auto item : Items) { //출력할 내용 없음 cout << item << endl; } }
Unordered Map/Set
- 기존의 자동 균형 트리의 사용으로 있던 정렬 기능이 빠진 map/set이다.
- Red-Black tree 대신 해시 테이블을 사용하는데, 정렬 상태를 보장하지 못하는 대신 랜덤접근의 속도가 트리의 $O(log n)$ 에서 해시의 “평균” $O(1)$ 로 많이 좋아진다.
- 하지만 같은 해시값이 존재하는 해시 충돌시에는 최악의 케이스일 때 O(n)으로 기존 트리구조보다 느리다. 때문에 성능이 더 중요하면 커스텀 해시함수를 작성해 사용하거나, 일반 map구조가 더 좋을 수 있다.
- 정렬 상태가 필요 없고(순회시 순서가 상관이 없고) 자주 검색할 일만 많다면 오히려 좋은 선택지일 수 있다.
- 사용법은 map/set과 동일하다.
활용
- 게임의 아이템, 퀘스트 목록, 환경설정등 데이터를 관리하는데 유용하게 쓰인다.
- 인벤토리를 구현할 때 아이템을 모두 보관하는 벡터가 아닌, 아이템의 ID와 갯수로 이루어진 map, 그리고 그 ID로 이루어진 아이템 정보 테이블로 구성하면 다양한 아이템을 필요로하는 MMORPG같은 환경에 잘 대응할 수 있다.
- 언리얼에서는 TMap, TSet이라는 대응되는 컨테이너를 제공한다.
- 웹/서버 개발에서도 key-value구조의 데이터를 저장할 때 쓰인다.
- 여러 분야에서 보편적으로 데이터 저장의 쓰이는 JSON도 같은 구조를 가지고있다.
- 서버의 캐시로 쓰이는 Redis도 해시 테이블 구조이다.
- URL 쿼리, HTTP 헤더, 쿠키등 여러 데이터도 모두 key-value의 형태를 띄고있다.
- 컴퓨터 과학 전반에 있어서도, 데이터와 숫자의 연결이라는 구조는 기초적인 자료구조로 쓰인다.
- DB의 인덱스
- 무결성 검증용 체크썸(Check Sum)
- 암호
- 블록체인
- 게임의 아이템, 퀘스트 목록, 환경설정등 데이터를 관리하는데 유용하게 쓰인다.
This post is licensed under CC BY 4.0 by the author.