CPP Module 09 (1)

folder_open 42Seoul / 42cursus | date_range / created at

sell #42seoul #42서울 #born2code #42cursus #CPP Module 09 #CPP Module #C++ #CPP #Bitcoin Exahange #Stack #자료구조

0. 시작하기 전에..

갑자기 Libft 다음에 CPP Module 09로 넘어왔다. CPP Module 09를 진행하다 보니, 이 과제는 바로 정리하지 않으면 까먹을 확률이 상당히 높을 거 같아서 먼저 기록하는 것으로 결정했다.
다만, 앞으로 포스트의 순서를 뒤에서부터 기억이 있을 때 기록할 지, 앞에서부터 복습하는 겸 기록할 지는 차차 생각해봐야 할 거 같다.

아마 이 과제는 제대로 구현한 사람들이 그리 많지 않을 것 같다. 나 역시 제대로 구현했는지 확신할 수는 없지만, 굉장히 공을 들였고 시간을 갈아넣었기에 어느정도는 참고할 정도는 될 것이라 생각한다.

1. 과제

CPP Module 08, 09는 STL의 사용이 허용된다.

Module-specific rules

각 exercise는 하나 이상의 컨테이너를 사용해야 한다. 단, ex02는 컨테이너 두 개를 사용해야 한다. 각 exercise에서 사용한 컨테이너는 다른 exercise에서 사용해서는 안된다.

std::pair는 컨테이너가 아니다..! 구글링을 해보자.

Exercise 00: Bitcoin Exchange

Category Content
Turn-in directory ex00/
Files to turn in Makefile, main.cpp, BitcoinExchange.{cpp, hpp}
Forbidden functions None

주어진 data.csv파일을 파싱하여, input으로 들어온 파일의 날짜에서 가장 가까운 날짜의 값을 계산하는 문제이다.
input의 경우, 다음과 같은 형식으로 들어온다.

example.csv

date | value
2011-01-03 | 3
2011-01-03 | 2
2011-01-03 | 1
2011-01-03 | 1.2
.
.
.

주의해야 할 점이 몇 가지 있다.

  • 가장 가까운 날짜를 찾되, lower date가 아닌 upper date를 찾아야 한다.
  • 이상한 input에 대한 예외처리를 해야 한다.
  • 범위는 0 ~ 1000 이다.

input의 형식을 보아하니, map 컨테이너를 사용하는 것이 적절할 것 같아 map을 사용하였다. 또한, 예외처리가 주인 과제가 아니다보니, 서브젝트의 요구사항을 만족할 정도로만 예외처리를 하였다.

먼저, data가 들어온 것을 파싱하여 정보를 저장해준다.

std::string line;
while (std::getline(csvFile, line)) {
	std::string key = line.substr(0, line.find(','));
	if (key == "date") {
		continue;
	}
	std::istringstream value(line.substr(line.find(',') + 1));
	float value_f;
	value >> value_f;
	csvData[key] = value_f;
}

지금 생각해보니 substr를 사용하지 않아도 istringstream을 사용하면 됐을 것 같다.
그럼 이제 해당 정보를 바탕으로 input파일의 값을 계산해야 한다. 역시 먼저 파싱을 해준다.

std::istringstream iss(line);
std::string key;
std::string value;
getline(iss, key, '|');
getline(iss, value, '|');

이제 들어온 값을 적절히 예외처리 해주면 된다. 이 부분은 따로 코드를 올리지는 않겠다.

그 다음 가장 가까운 날짜를 찾아야 할텐데, 대부분 string의 값을 비교해 가장 가까운 날짜를 찾는 것 같았다. 다만, 나는 그런 방법을 생각하지 못했기에, 조금 어렵게 접근한 것 같다. time_t를 활용해, struct tm의 tm_year, tm_mon, tm_mday를 init해주었다. 이렇게 하니 각 요소에 대한 예외처리를 하기는 수월했다.

struct tm to_return;
std::memset(&to_return, 0, sizeof(to_return));
std::string::iterator date_iter = date.begin();
std::stringstream ss;
while (*date_iter != '-') {
	if (*date_iter != '-' && !std::isdigit(*date_iter))
		return NULL;
	ss << *date_iter;
	date_iter++;
}
ss >> to_return.tm_year;
ss.str("");
ss.clear();
if (to_return.tm_year < 2010 ||to_return.tm_year > 2024)
	return NULL;
date_iter++;
while (*date_iter != '-') {
	if (*date_iter != '-' && !std::isdigit(*date_iter))
		return NULL;
	ss << *date_iter;
	date_iter++;
}
ss >> to_return.tm_mon;
ss.str("");
ss.clear();
if (to_return.tm_mon <= 0 || to_return.tm_mon > 12)
	return NULL;
date_iter++;
while (date_iter != date.end()) {
	if (*date_iter != '-' && !std::isdigit(*date_iter))
		return NULL;
	ss << *date_iter;
	date_iter++;
}
ss >> to_return.tm_mday;
ss.str("");
ss.clear();
if (to_return.tm_mday <= 0 || to_return.tm_mday > 31)
	return NULL;
return std::mktime(&to_return);

그 후 std::difftime을 활용하여 날짜간의 차이를 계산해주었다.

std::string to_return;
time_t s_date = keyToDate(date);
if (!s_date) {
	throw badInputException(date);
}
std::map<std::string, float>::iterator map_iter = getCsvData().begin();
time_t map_iter_date = keyToDate(map_iter->first);
double min_sub = std::numeric_limits<double>::max();
double cal = std::difftime(s_date, map_iter_date);
while (cal >= 0 && map_iter != getCsvData().end()) {
	if (cal < min_sub) {
		min_sub = cal;
		to_return = map_iter->first;
	}
	map_iter++;
	map_iter_date = keyToDate(map_iter->first);
	cal = std::difftime(s_date, map_iter_date);
}
return to_return;

이제 가져온 값을 바탕으로 계산해주면 된다.

좀 대충 쓴 감이 없지않아 있는데, 맞다. 사실 이 exercise는 나에게 진짜 재미가 없었다.

Exercise 01: Reverse Polish Notation

번역하면 역폴란드표기법 정도 되겠다. 구글링 해보면 후위 표기식이라고 나온다.
Exercise 01은 stack 컨테이너를 선택하였다. 사실 컨테이너를 선택하는 것에 큰 고민이 없었는데, 후위 표기식 같은 경우 워낙 유명한 알고리즘 문제인지라 이전에 문제를 풀었던 경험으로 stack을 선택했다. 이 Exercise를 하시는 분들은 왜 stack을 사용했는지 고민해보길 바란다.

주의해야할 점은 다음과 같다.

  • 괄호는 취급하지 않는다.
  • 숫자는 1 ~ 9 사이이다.
  • Zero Division을 조심하자.

로직은 다음과 같다.

  • 숫자가 들어오면 stack에 push한다.
  • 연산자가 들어오면 stack에서 두 번 pop하여 들어온 연산자로 계산한 후 push한다.
  • 모두 연산한 후 stack의 사이즈가 1이 아니면 뭔가 식이 잘못된 거다.
  • 중간에 pop할 숫자가 2개가 안되면 뭔가 식이 잘못된 거다.

코드는 다음과 같다.

std::string::iterator Iter = input.begin();

for (Iter = input.begin();Iter != input.end();Iter++) {
	if (std::isdigit(*Iter)) {
		if (std::isdigit(*(Iter + 1)))
			throw invalidInputException();
		Stack.push(*Iter - '0');
	}
	else if (*Iter == 42 || *Iter == 43 || *Iter == 45 || *Iter == 47) {
		if (std::isdigit(*(Iter + 1)))
			throw invalidInputException();
		double num1, num2;
		if (Stack.size() < 2) {
			throw invalidInputException();
		}
		num1 = Stack.top(); Stack.pop();
		num2 = Stack.top(); Stack.pop();
		double result = operation(num1, num2, *Iter);
		if (result == -1) {
			throw invalidInputException();
		}
		Stack.push(result);
	}
	else if (*Iter == ' ')
		continue;
	else
		throw invalidInputException();
}
if (Stack.size() != 1) {
	throw invalidInputException();
}
return Stack.top();

이제 다음 글 부터는 그 악명 높은 포드 존슨에 대해 쓰도록 하겠다.