Map

자료구조 중 하나인 Map은 키(key)와 값(value)를 쌍으로 갖는 STL입니다. Map의 특징 중 하나는 키 값이 중복되지 않는 다는 것입니다. C++에 있는 Map은 레드블랙트리로 이루어져있으며 검색, 삽입, 삭제가 O(log n)입니다.

코드를 보면서 어떻게 사용할 수 있는지 확인해보도록 하겠습니다. map을 사용하기 위해서는 아래와 같이 map 헤더파일을 include해주어야합니다.

#include <map>

 

1. 데이터 삽입, 조회, 변경

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
	map<string, string> m;
	m["seoul"] = "02";
	m["kyungki"] = "031";
	m["daegu"] = "051";
	m["incheon"] = "032";
	
	//맵의 모든 데이터를 순환
    //first, second를 보아 pair객체를 사용하는 것을 알 수 있다.
	for (auto iter : m) {
		cout << iter.first << "의 지역번호:" << iter.second << endl;
	}
	cout << endl;

	//[]로도 접근 가능
	cout <<"seoul:"<< m["seoul"] << endl;

	//변경
	m["daegu"] = "053";
	cout << "daegu:" << m["daegu"] << endl;

}

배열 인덱스를 다루듯이 사용할 수가 있습니다.

아래는 결과화면입니다.

 

그런데 한가지 유심히 보면 map의 키,값을 순회할때 키가 오름차순으로 나오고 있네요(daegu - incheon - kyungki - seoul).  내림차순으로 map을 구성하고 싶다면 아래와 같이 사용하세요.

map<string, string,greater<string>> m;

그리고 insert()함수를 통해서 삽입할 수도 있습니다. Map은 내부적으로 pair 객체를 이용하여 키와 값을 저장하는데요. first는 키, second는 값이 들어가게 됩니다.

#include <iostream>
#include <map>
#include <string>
using namespace std;

int main() {
	map<string, string> users;
	users.insert({ "010-3343-1111","reakwon" });
	users.insert({ "010-1133-0000","lee" });
	users.insert({ "010-1999-9991","huh" });
	users.insert({ "010-1192-1928","so" });
	users.insert(make_pair<string, string>("010-4444-4455", "kim"));
	
	for (auto it : users)
		cout << "name:" << it.second << ", phone:" << it.first << endl;

}

 

2. 키가 존재하는지 확인

find함수로 키가 존재하는지 확인할 수 있습니다. iterator로 내부적으로 순환하면서 비교하고 찾기 때문에 만약 키가 존재하지 않으면 iterator의 end()를 반환하게 됩니다.

	if (users.find("010-1133-0000") != users.end()) {
		cout << "키가 존재" << endl;
	}
	else {
		cout << "키가 존재하지 않음" << endl;
	}

 

3. 키-값 삭제

삭제 연산은 erase함수로 키과 값 쌍을 삭제할 수 있습니다. 

3-1. 입력된 키와 같은 pair를 삭제

users.erase("010-3343-1111");

 

3-2. 데이터 모두 삭제

users.erase() 혹은 clear() 함수로 데이터를 모두 삭제할 수 있습니다. 아래와 같이 사용합니다.

users.erase(users.begin(),users.end());
users.clear();

 

4. 저장된 키-값 수 

size() 함수를 사용하면 현재 map에 데이터가 저장된 수를 알 수 있습니다.

users.size()

 

Map을 이용한 문제풀이

Map을 사용하면 아래의 문제를 쉽게 풀수가 있습니다. 한번 문제보시면서 풀어보시구요. 정답 코드는 아래에 있습니다.

https://www.acmicpc.net/problem/1764

 

1764번: 듣보잡

첫째 줄에 듣도 못한 사람의 수 N, 보도 못한 사람의 수 M이 주어진다. 이어서 둘째 줄부터 N개의 줄에 걸쳐 듣도 못한 사람의 이름과, N+2째 줄부터 보도 못한 사람의 이름이 순서대로 주어진다.

www.acmicpc.net

#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;

int n, m;
int main() {
	cin >> n >> m;
	//값은 0으로 default로 설정됨
	map<string, int> outsiders;
	vector<string> ans;
	
	for (int i = 0; i < n+m; i++) {	//듣+보 전부 한꺼번에 입력받음(n+m)
		string name;
		cin >> name;		//이름이 2번 등장하면 outsiders[name] = 2가 된다
		outsiders[name]++;	
	}
	//2면 정답
	for (auto it : outsiders) {
		if (it.second == 2) ans.push_back(it.first);
	}
	
	cout << ans.size() << endl;
	for (int i = 0; i < ans.size(); i++)
		cout << ans[i] << endl;

}

 

이상으로 포스팅 마치도록 하겠습니다~

반응형
블로그 이미지

REAKWON

와나진짜

,

Vector

Standard Template Library의 컨테이너로 정의된 클래스인데요. 배열과 비슷한 특징이 있습니다만, 동적으로 계속하여 뒤에 원소를 추가할 수 있습니다. 배열을 다루는 사용자의 불편함을 vector를 사용하면 어느정도 편리하게 사용할 수 있습니다. 이 포스팅에서는 vector의 사용방법에 대해서 다룹니다.

C++에서 vector를 사용하기 위해서는 아래와 같이 vector 헤더파일을 추가시키시면 됩니다.

#include <vector>

 

1. 초기화

배열과 비슷하다고 했습니다만 초기화 방법에서는 약간 차이가 있습니다. 아래의 코드는 초기화 방식을 설명합니다. 

vector<int> v1;			//아무것도 없는 비어있는 vector
vector<int> v2(5);		//5개의 int형을 저장하는 vector(전부 0으로 초기화)
vector<int> v3(5,1);	//5개의 int형을 저장하는 vector(전부 1로 초기화)
vector<int> v4 = { 1,2,3,4,5 };	//배열과 같은 초기화
vector<int> v5(v4);		//v4의 벡터 요소를 복사해서 초기화

 

2. 크기와 용량(size & capacity)

vector는 현재 가지고 있는 데이터의 수를 나타내는 크기(size)와 얼만큼의 데이터를 담을 수 있는지에 대한 용량(capacity)가 있습니다. 만약 용량이 전부 꽉 차게 되면 용량을 동적으로 더 늘려서 데이터를 추가할 수 있습니다. vector의 용량은 항상 size보다 크거나 같습니다. 아래의 그림처럼 capacity가 모자라게 되면 늘리게 되는거죠.

 

코드로 직접 확인해보세요.

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v;
	for (int i = 0; i < 20; i++) {
		v.push_back(i + 1);
		cout << " 용량 :" << v.capacity();
		cout << " 크기 :" << v.size();
		cout << " 데이터: " << v[i] << endl;
	}
	return 0;
}

 

 

3. 데이터 읽기

데이터읽는 방법은 배열과 같이 []로 접근하는 방법과 at() 으로 접근하는 방법이 있습니다. 둘은 같은 값을 나타내줍니다. 아래의 코드를 보고 결과가 같은지 확인해봅시다.

int main() {
	vector<int> v = { 1,5,3,6,8 };
	cout << "v[1]:" << v[1] << endl;
	cout << "v.at(1):" << v.at(1) << endl;
	cout << "v[3]:" << v[3] << endl;
	cout << "v.at(3):" << v.at(3) << endl;

	return 0;
}

 

둘은 같은 값을 나타내고 있죠? 하지만 차이는 없을까요? 있겠죠. 만약 배열 접근 기호([])로 10번째 요소를 읽어봅시다. 현재 5번째까지 초기화했고, 10번째는 아직 접근할 수 없기 때문에 아래와 같은 에러를 보이고 종료하고 맙니다. at()통해서도 마찬가지일거에요. 하지만 둘의 차이는 예외를 뜨게해서 처리할수 있게 만들었느냐 아니냐입니다.

아래의 코드를 실행시켜보시고, 바꿔서 윗줄은 주석처리, 아랫줄은 주석 해제하여 실행해보세요. 차이점을 알 수 있습니다.

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v = { 1,5,3,6,8 };
	try {
		cout << v[10] << endl;
		//cout << v.at(10) << endl;
	}
	catch (out_of_range& e) {
		cout << "예외 발생 처리 " << endl;
	}

	return 0;
}

 

프로그래밍을 at()으로 하면 더 안전하게 사용할 수 있는 대신 검사때문에 []를 이용하는 방법보다는 느립니다. 두 방식 중 알맞게 선택하여 사용하세요.

4. 데이터쓰기

데이터쓰기는 너무 편합니다. 그냥 배열과 같이 사용하면 됩니다.

	vector<int> v = { 5,3,1,6,7 };
	v[2] = 3;

 

5. 데이터 뒤에 추가 및 뒤에서 삭제

push_back()을 사용하면 아래와 같이 vector 뒤에 차곡차곡 데이터를 추가합니다. 반대로 삭제하려면 pop_back()을 사용하시면 됩니다. pop_back()은 데이터를 return하지는 않고, 단지 꺼내주기만 합니다. 가장 마지막 원소를 가져오려면 back()을 이용하세요.

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v;

	v.push_back(10);
	v.push_back(13);
	v.push_back(15);
	v.push_back(20);

	int size = v.size();
	for (int i = 0; i < size; i++) {
		cout << "back():" << v.back() << endl;
		v.pop_back();
	}

	return 0;
}

 

6. Iterator 

Iterator를 통해서 for문을 돌수도 있습니다. vector의 begin()은 vector의 처음 요소를 가리키고 있습니다. vector의 end()는 vector의 마지막 요소 다음을 가리키고 있습니다. 마지막 요소 다음이지 마지막 요소를 가리키는게 아닙니다.

그래서 아래와 같이 for문을 사용하는 방법이 가능합니다.

vector<int> v = { 0,9,21,1,0,29 };
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) 
	cout << *it << endl;

 

너무 복잡하죠? 아래와 같이 auto로 코드를 줄일 수 있습니다.

for (auto it = v.begin(); it != v.end(); it++)
	cout << *it << endl;

 

7. reserve

용량을 지정한 수대로 동적할당을 미리 시켜놓습니다. 

int main() {
	vector<int> v = { 0,9,21,1,0,29,2022 };
	cout << "capacity:" << v.capacity() << endl;
	v.reserve(10);
	cout << "capacity:" << v.capacity() << endl;
	v.reserve(15);
	cout << "capacity:" << v.capacity() << endl;

	return 0;
}

 

8. insert

insert는 iterator를 통해서 원소를 삽입하는 방식입니다. 아래와 같이 사용가능합니다. 

#include <iostream>
#include <vector>

using namespace std;

int main() {
	vector<int> v = { 0,9,21,1,0,29,2022 };
	vector<int>::iterator it=v.begin();	//맨앞
	//v.insert(it, 90);	//맨앞에 90삽입
	v.insert(it + 4, 90);	//4번째 원소에 90삽입

	for (auto it = v.begin(); it != v.end(); it++)
		cout << *it << endl;

	return 0;
}

이 밖에도 insert는 오버로딩이 되어있으므로 찾아보셔서 알맞는 것을 사용하면 됩니다.

 

9. 정렬

vector는 algorithm 헤더파일의 sort()함수로 정렬이 가능합니다. 다만 기본 자료형만 가능하다는 점이고, 클래스같은 경우는 별도로 비교 함수를 사용하여 delegator 방식으로 처리해야합니다.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
	vector<int> v = { 0,9,21,1,0,29,2022 };
	sort(v.begin(), v.end());
	for (auto it = v.begin(); it != v.end(); it++)
		cout << *it << endl;

	return 0;
}

이 밖에도 insert는 오버로딩이 되어있으므로 찾아보셔서 알맞는 것을 사용하면 됩니다.

 

그외에도 여러가지 vector 관련 함수들이 있는데요. 어렵지 않은 함수들이니까 나중에 직접 찾아서 활용해보시기 바랍니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

조건문 if

C언어를 배우는 중에 조건에 따라 실행흐름을 분기시키는 예약어인 if문에 대해서 자세히 알아보도록 합시다. if문와 else if는 괄호에 조건식을 써주는데요. 이 조건에 따라 실행을 다르게 시킬 목적으로 사용이 됩니다. 아래와 같은 방식으로 사용하게 됩니다. 이제 예제를 보면서 하나씩 이해하도록 합시다.

if(조건 1){
	//실행 1//
} else if(조건 2){
	//실행 2//
} else if(조건 3){
	// ... //
} else if(조건 n){
	//실행 n//
}else{
	//그 외 실행
}

 

1. if 단독 사용법

if문은 다음과 같이 괄호 안에 비교식을 써주면 되는데요. 이때 괄호식 안에 조건이 참이 되는 경우에만, if안의 코드를 실행하게 됩니다. if 안의 문장이 한문장일 경우에 아래의 중괄호는 생략이 될 수 있습니다.

#include <stdio.h>

void main() {
	int a = 10;
	if (a == 10) {	//if안의 구문이 한 줄이면 '{' 생략가능 
		printf("a = 10\n");
	}//if안의 구문이 한 줄이면 '}' 생략가능
	printf("종료\n");
}

위의 코드는 아래와 같이 수행이 됩니다. a가 5라면 if안에 printf는 호출이 되지 않지요.

 

2. if ~ else ~ 구문 예제

if에 조건에 맞지 않는 조건을 수행할 때에는 else 구문을 이용할 수 있습니다. 

#include <stdio.h>

void main() {
	int a = 2;
	if (a == 10) {	
		printf("a의 값은 10.\n");
	}
	else {
		printf("a의 값은 10이 아님.\n");
	}
}

아래는 위 코드의 결과입니다.

 

3. 조건이 여러가지일 경우 else if

조건이 여러 가지가 있을 경우에는 else if를 통해서 여러 조건을 줄 수가 있습니다. if 부터 순차적으로 아래쪽으로 비교해서 그 중 참이 되는 경우에 있는 블록만 실행하고 바로 if문을 빠져나옵니다.

#include <stdio.h>

void main() {
	int a = 3;
	if (a >= 5) {	
		printf("a의 값은 5 이상\n");
	}
	else if (a >= 2) {
		printf("a의 값은 2 이상\n");
	}
	else {
		printf("a의 값은 2 미만\n");
	}
}

#include <stdio.h>

void main() {
	int score = 85;
	if (score >= 90) {
		printf("학점 A\n");
	}
	else if (score >= 80) {
		printf("학점 B\n");
	}
	else if (score >= 70) {
		printf("학점 C\n");
	}
	else if (score >= 60) {
		printf("학점 D\n");
	}
	else {
		printf("학점 F\n");
	}
}

 

4. 조건이 참인 경우 = 0이 아닌 경우

C, C++에서 거짓의 조건은 간단합니다. 0만 false라고 간주한다는 것이죠. 그 외의 모든 수는 전부 참으로 간주합니다. 즉, 여기 if문이나 else if에서 조건이 참인 경우는 0이 아닌 경우입니다. 0외에 숫자는 모두 참으로 간주하게 됩니다. 그래서 아래의 코드중 a와 b가 참이되는 이유입니다.

#include <stdio.h>

void main() {
	int a = 1;
	int b = -1;
	int c = 0;
	if (a) {
		printf("a는 true\n");
	}
	if (b) {
		printf("b는 true\n");
	}
	if (c) {
		printf("c는 true\n");
	}
	printf("종료\n");
}

 

5. &&조건과 || 조건

&&(논리 AND 연산)은 모든 조건이 전부 true여야 true이고, ||(논리 OR 연산)은 모든 조건중에 하나라도 true이면 true입니다. 

#include <stdio.h>

void main() {
	int a = 1;
	int b = 2;
	if (a == 1 && b == 2) {
		printf("a=1이고 b=2이다.");
	}
	else {
		printf("a=1이 아니거나, b=2가 아니다.");
	}
}

6.1 하기 쉬운 실수1 (모든 비교문이 수행이 되지 않음)

다음은 if문이나 조건식에서 흔하게 하기 쉬운 실수입니다. 아래의 코드를 예측해보시기 바랍니다.

#include <stdio.h>

void main() {
	int a = 10;
	int b = 20;
	if (a > 10 && (b++) > 20) {
		printf("실행이 될까요?\n");
	}
	printf("b의 값:%d\n", b);
}

 

조건을 보게 되면 a는 10보다 크지 않죠. 딱 10이니까요. 그렇다면 a>10은 false가 됩니다. 

그리고 다음 조건에서 b를 선증가시킨 후 20과 비교합니다. b는 21의 값이 되겠네요. 그렇다면 b>20의 조건은 true가 되겠네요. 

그렇다면 if문 안의 printf는 실행이 되지 않겠네요. 그렇다면 b의 값은 얼마일까요? 아래 결과 화면입니다.

 

b의 값은 고스란히 20입니다. 조건식에서 비교할때 &&이나 ||이 오게 되면 true나 false가 확정일 경우에는 그 뒤의 식은 보지도 않고 건너 뛰어버립니다. 그래서 b++은 수행되지 않게 되죠. 그래서 b가 21로 나오게 할 경우에는 아래와 같이 수정이 되어야 21이 나오게 됩니다.

#include <stdio.h>

void main() {
	int a = 10;
	int b = 20;
	if (a > 9 && (b++) > 20) {
		printf("실행이 될까요?\n");
	}
	printf("b의 값:%d\n", b);
}

6.2 하기 쉬운 실수 2 (실수로 값 대입)

== 연산자나 != 연산자를 사용할때 흔히 이런 실수를 많이 하게 됩니다. 아래의 코드는 원래 finished == 1일 경우 프로그램이 종료되야하는 조건입니다. finished가 그전에 0이기 때문에 if문을 수행하지 않는것이 프로그래머의 의도입니다. 하지만 코드에서 프로그래머의 실수로 '=' 하나 빠지는 바람에 finished=0에서 finished=1로 대입이 되었고, if문이 실행이 됩니다.

#include <stdio.h>

void main() {
	int finished = 0;
	if (finished = 1) {	//이 식은 문법상 문제가 없는 문장이므로 에러없이 컴파일됨
		printf("finished\n");
	}
}

 

이런 오류들은 컴파일에서 잡아낼 수 없으며 찾는데도 오래 걸리게 됩니다. 따라서 비교식을 쓸때 상수는 왼쪽에 쓰는 것이 이런 휴먼 에러를 막을 수 있습니다.

이렇게 빨간색으로 뜨니까 컴파일단계에서 바로 알 수 있고 아래처럼 올바르게 수정하여 프로그램을 더 안전하게 만들 수 있습니다.

#include <stdio.h>

void main() {
	int finished = 0;
	if (1 == finished) {
		printf("finished\n");
	}
}

 

여기까지 C언어의 if문에 대해서 알아보았습니다. if문은 어렵지는 않지만 비교문이 끝까지 비교가 진행이 되는지 되지 않는지 모르는 것과 아는 것은 다릅니다. 공부하는데 도움이 되었으면 좋겠습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,