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();
이제 다음 글 부터는 그 악명 높은 포드 존슨에 대해 쓰도록 하겠다.