6.1주차
실습 13
두 점이 입력되면 몇 사분면에 해당하는지 구하는 문제. 입력값에 0은 없다.
내가 제출한 코드
넘 쉬워성,, vscode에서 풀지도 않음,, ㅎ
실습 14
조합을 구하는 문제이다. 여기서 문제는 OverFlow 문제,,, 아무도 해결 못함,,,
근데 여튼 여기서 배운 점은 예외 처리 하는 방법이다.
실습 14를 활용한 예외 처리 방법
기본 코드
#include <iostream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int product(int from, int count)
{
int answer = 1;
int temp;
for (int i = 0; i < count; i++)
{
temp = answer * (from - i);
answer = temp;
}
return answer;
}
int solution(int balls, int share)
{
int answer;
int a = product(balls, share);
int b = product(share, share);
answer = a / b;
cout << "answer: " << answer << endl;
return answer;
}
int main()
{
int balls, share;
cin >> balls >> share;
cout << solution(balls, share);
}
product() 함수를 통해서 from 부터 1씩 작아지면서 count 만큼 곱해진 값이 나온다. 예를 들어서 5C3인 경우 product(5, 3)인 경우는 5 _ 4 _ 3이고 product(3, 3)인 경우는 3 _ 2 _ 1 의 결과값을 나오게 한다. 여튼 이렇게 하면 조합의 값을 구할 수 있는데, 계속 overflow 에러가 발생했다.
예외 가능성
- 입력값이 음수인 경우 -> incorrectInput()
- balls보다 share의 개수가 많은 경우 -> incorrectInput()
- overflow가 발생한 경우 -> 곱해지기 전보다 곱해진 후의 값이 크다면 overflow로 평가.
클래스를 아직 모르는 입장에서 예외처리는 어케 하냐?
step 1.
class incorrectInput
{
};
class overflowError
{
};
처리하고 싶은 에러의 이름을 가지고 있는 클래스를 만든다.
step 2.
for (int i = 0; i < count; i++)
{
temp = answer * (from - i);
if (temp < answer)
throw overflowError();
answer = temp;
}
코드를 실행하다가 내가 처리하고 싶은 에러가 있다면 그 부분에서 throw를 통해서 에러를 던져준다.
step 3.
try
{
int a = product(balls, share);
int b = product(share, share);
answer = a / b;
}
catch (overflowError e)
{
cout << "OverFlow Error" << endl;
}
catch (incorrectInput e)
{
cout << "OverFlow Error from solution" << endl;
}
throw가 발생하는 순간 돌고 있던 그 함수에서는 나오고 바깥 함수로 나오게 된다. 여기서 throw된 에러와 같은 에러는 catch 하고 있다면 여기서 catch안에 있는 코드로 적절한 조치를 취해준 후, 다음 코드로 정상 진행 이 된다.
#include <iostream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class incorrectInput
{
};
class overflowError
{
};
int product(int from, int count)
{
int answer = 1;
int temp;
for (int i = 0; i < count; i++)
{
temp = answer * (from - i);
if (temp < answer)
throw overflowError();
answer = temp;
}
return answer;
}
int solution(int balls, int share)
{
int answer;
if (balls <= 0 || share <= 0)
throw incorrectInput();
try
{
int a = product(balls, share);
int b = product(share, share);
answer = a / b;
}
catch (overflowError e)
{
cout << "OverFlow Error" << endl;
}
catch (incorrectInput e)
{
cout << "OverFlow Error from solution" << endl;
}
cout << "answer: " << answer << endl;
return answer;
}
int main()
{
int balls, share;
cin >> balls >> share;
try
{
cout << solution(balls, share);
}
catch (incorrectInput e)
{
cout << "Incorrect Error from Main" << endl;
}
}
예외 처리 구체적인 진행 과정
예외 처리: 문제를 미리 예상 조건을 비교해서 적절한 exception을 던지는게 첫 번째 목표 = 지금 무슨 문제 때문에 발생했는지 알려주고 싶어 => 그래서 에러 명을 따로 클래스로 만들어준 것이다. 클래스를 통해서 누가 throw 한 에러인지를 판단한다.
예를 들어서 product에서 throw가 발생한다면 그 순간 바로 그 함수에서 exit을 해버린다. 즉, throw 가 있는 그 함수에서의 모든 코드는 바로 무시가 된다. 그러면서 실행했던 product는 바로 종료되면서 그 함수에서는 incorrectInput이라는 객체만 날라가게 된다. product 함수가 정상 종료가 되지 않았기 때문에 product 바깥 함수인 solution 역시 그 아래 코드들이 모두 무시된다. 즉, try & catch가 없다면 모든 함수로부터 무서운 속도로 빠져나와 exception object를 던지게 된다. 그렇다면 여기서 이 객체를 멈추게 하고 싶다면 try & catch를 이용하면 된다. try 라고 되어 있는 코드 안에는 발생할 수 있는 exception을 throw할 수 있는 코드가 있다. 만약 try에서 예외 객체가 날라온다면 catch를 통해서 잡아줄 수 있다. 여기서 catch는 모든 예외들을 잡는 것이 아니라 특정 예외만 잡을 수 있다. 여튼 catch에 예외가 들어온다면 catch안에 코드를 통해서 적절한 조치 후 정상 종료를 가능하게 한다.
가장 바람직한 시나리오는 적절한 예외를 깨닫고, 이에 따른 적절한 예외를 처리하는 것이다. 이로써 정상 운영/진행이 목적이기 때문에 따라서 throw를 던지는 순간 무조건 프로그램 종료가 되지만 이를 catch를 통해서 어떤 예외가 발생했으며, 프로그램 종료를 막고, 정상적인 상황 처리를 해줌으로서 정상 종료가 되도록 해준다.
try에서 날라오는 exception을 catch가 잡아준다. 문제는 catch가 골라잡는다는 것이다. solution에서의 첫 번째 catch는 overflorError 에러만 잡고, 두 번째 catch에서는 incorrectInput 에러만 잡는다. catch 안에 문장이 수행이 된다면 이제는 예외 상황이 아니기 때문에 catch 다음 문장으로 넘어갈 때는 정상 상황으로 넘어가게 된다. “이제부터는 정상 상황입니다^^” 한 번잡아주면 그 다음부터 정상 플로우로 복귀하게 된다 문제의 원인을 구분해서 추가적인 정보를 주기 위해서 클래스를 만드는 것이다. 부가적인 정보를 더 보낼 수 있긴함. 근데 클래스를 배우지 않았기 때문에 이거에 대한 설명은 하지 않았다. 클래스를 통해서, 상속을 통해서 예외처리를 좀 더 구체적으로 할 수는 있다.
❓ 만약 input이 음수로 인한 에러가 뜬다면 product : exit -> solution : exit -> main 에서 catch 로 넘어가는거? ooo 만약에 solution에 incorrectInput 처리가 없다면 질문과 같은 플로우로 진행되게 된다.
incorrectInput에 대한 catch를 solution과 main으로 둘 다 했을 때 위에서 먼저 잡는 순간 이미 정상 상황으로 돌아간 것이다. throw는 한번 잡히면 거기서 소멸된다. 잡을 때 자기의 타입이 정해져있다. catch가 잡을 수 있는 애가 정해져 있다. 바람직하지 않지만 자주 쓰는 방법이 있다. 뭐든 잡겠어!!!!!!!!! 그 문법이 있다.
catch(…) {}; -> 이거임. 이걸 즐겨쓰는거는 권장되지 않아. 원인을 모르게되니까 최악의 경우를 막기 위해서 일단 잡는 것이지 뭐가 날라온지를 몰라서 그 상황에 맞는 적절한 조치를 할 수 없다. 그래서 일반적으로 권장되지는 않는다.
지금까지
- throw는 무엇인가
- 클래스로 예외를 구별하는 의미
- catch는 어느 영역을 감쌓야되는지
- catch에 의해서 잡히지 않으면 어떻게 동작하는지, 잡히면 어떻게 동작하는지
에 대해서 배웠다.
C와 C++의 차이
C는 예외 처리와 유사하게 구현은 가능하지만 구현법이 정상 플로우를 따라가게 되어있다. 정상 플롱를 따라간다는 말은 C++처럼 throw하면 중간에 함수를 종료시키는 return, exit이 필요하지 않은 것이아니라 반대로 return과 exit을 통해서 예외를 처리한다는 말이다. 즉, C의 경우는 리턴값을 판단하고 그런 다음 그 리턴값이 에러라면 이렇게 처리하고,, 이렇게 해야되는데 단점은 코드를 읽는 사람이 정상적인 플로우인지 예외에 대한 처리를 위한 플로우인지를 확인하기가 힘들다. C++처럼 가독성이 좋도록 만드는 코드가 예쁜 코드다.
throw = 문제, try = 의심 구간, Catch = 문제 해결/처리
과제 4-1
문제 설명
한 개 이상의 항의 합으로 이루어진 식을 다항식이라고 합니다. 다항식을 계산할 때는 동류항끼리 계산해 정리합니다. 덧셈으로 이루어진 다항식 polynomial이 매개변수로 주어질 때, 동류항끼리 더한 결괏값을 문자열로 return 하도록 solution 함수를 완성해보세요. 같은 식이라면 가장 짧은 수식을 return 합니다.
제한사항
0 < polynomial에 있는 수 < 100
polynomial에 변수는 ‘x’만 존재합니다.
polynomial은 0부터 9까지의 정수, 공백, ‘x’, ‘+’로 이루어져 있습니다.
항과 연산기호 사이에는 항상 공백이 존재합니다.
공백은 연속되지 않으며 시작이나 끝에는 공백이 없습니다.
하나의 항에서 변수가 숫자 앞에 오는 경우는 없습니다.
” + 3xx + + x7 + “와 같은 잘못된 입력은 주어지지 않습니다.
“012x + 001”처럼 0을 제외하고는 0으로 시작하는 수는 없습니다.
문자와 숫자 사이의 곱하기는 생략합니다.
polynomial에는 일차 항과 상수항만 존재합니다.
계수 1은 생략합니다.
결괏값에 상수항은 마지막에 둡니다.
0 < polynomial의 길이 < 50
입출력 예
polynomial | result |
---|---|
“3x + 7 + x” | “4x + 7” |
“x + x + x” | “3x” |
내가 제출한 코드
#include <iostream>
#include <string>
#include <vector>
using namespace std;
string getAnswer(int x, int const_num)
{
string answer = "";
string str_x = "";
string str_const = to_string(const_num);
// x 계수가 1인 경우 처리
if (x != 1)
{
str_x = to_string(x);
}
// 0인 경우는 답에 포함시켜서 리턴시킴 안됨
if (x > 0 && const_num > 0)
{
answer += str_x;
answer += "x + ";
answer += str_const;
}
else if (x > 0 && const_num == 0)
{
answer += str_x;
answer += "x";
}
else if (x == 0 && const_num > 0)
{
answer += str_const;
}
return answer;
}
string solution(string polynomial)
{
string answer = "";
int x_num = 0;
int const_num = 0;
string temp = "";
for (int i = 0; i < polynomial.size(); i++)
{
if (polynomial[i] >= '0' && polynomial[i] <= '9')
{
temp += polynomial[i];
}
else if (polynomial[i] == 'x')
{
if (temp == "")
{
x_num += 1;
}
else
{
x_num += stoi(temp);
}
cout << "x_num: " << x_num << endl;
temp = "";
}
else if (polynomial[i] == ' ' && temp != "")
{
const_num += stoi(temp);
temp = "";
cout << "const_num: " << const_num << endl;
}
}
if (temp != "")
{
const_num += stoi(temp);
cout << "const_num: " << const_num << endl;
}
answer = getAnswer(x_num, const_num);
cout << "answer: " << answer << endl;
return answer;
}
반복문을 돌면서 해당 문자가 숫자인 경우는 temp에 넣다가, x나 temp가 빈 문자열이 아닌 공백인 경우에는 정수형으로 바꾸어서 해당 계수부분으로 넣는다.
그리고 한 번에 통과를 못한 이유는
- x의 계수나 상수가 0인 경우는 문자열에 포함시키면 안된다.
- x의 계수가 1인 경우에는 1x가 아니라 x로 나와야 된다.
교수님 코드
-
stoi 구현
// string -> int로 바꿔줌 int my_stoi(string num) { int k = 1; int n = 0; for (int i = num.length() - 1; i >= 0; i--, k *= 10) { n += (num[i] - '0') * k; } return n; }
일의 자리부터 인덱스를 잡아서 k를 통해서 숫자로 더했을 때의 자리수를 맞춘다.
-
itos 구현
// int -> string으로 바꿔줌 string my_itos(int n) { string num = ""; for (int i = n; i > 0; i /= 10) { char temp = (i % 10) + '0'; num = temp + num; } return num; }
이 코드의 경우는 숫자를 한 자리씩 더하는 코드에서도 사용이 되었는데, 한 자리를 때서 문자열로 바꾼다음
num = temp + num
을 통해서 숫자와 같은 문자열을 만들어준다.num += temp
로 하게 되면은 문자열이 기존 숫자와 달리 거꾸로 나옴(당연한 말!) -
tokenize 구현
vector<string> tokenize(string letter) { vector<string> tokens; int i, j; for (i = 0; i < letter.length(); i = j + 1) { for (j = i; j < letter.length(); j++) { if (letter[j] == ' ') { tokens.push_back(letter.substr(i, j - i)); break; } } } if (j == letter.length()) { tokens.push_back(letter.substr(i, j - i + 1)); } return tokens; }
문자열에 공백이 있다면 공백 앞쪽까지를 모두 tokens 벡터에 넣어준다.
이중포문을 사용을 하는데, 바깥 포문의 경우는 letter에서 토큰할 앞쪽 인덱스를 뜻하고, 안쪽 포문의 경우는 어디까지 토큰을 할지에 대한 인덱스를 뜻한다. 그래서 substr을 통해서 i부터 j - i의 길이만큼 토큰을 하게 된다.
substr(문자 첫 번째 인덱스, 문자열의 길이)
왜 j - i가 정확한 문자열의 길이냐를 봤을 때, 공백이 포함되어 있기 때문이다.그리고 마지막 if문의 경우는 마지막 토큰이 tokens 벡터에 들어갈 수 있게 하기 위한 토큰이다. 이 조건문이 없다면 마지막 항이 벡터에 들어가지 않음.
-
solution
string solution(string polynomial) { string answer = ""; int cof = 0; int constant = 0; vector<string> tokens = tokenize(polynomial); for (int i = 0; i < tokens.size(); i++) { if (tokens[i] == "+") { } // x에 대한 항인지를 물어보는 조건문 tokens[i]는 string인데 그 마지막 글자가 x인지 아닌지를 확인하고 싶다. else if (tokens[i][tokens[i].length() - 1] == 'x') { if (tokens[i].length() == 1) { cof++; } else { cof += my_stoi(tokens[i].substr(0, tokens[i].length() - 1)); } } else { constant += my_stoi(tokens[i]); } } // 출력 형식 맞추기 if (cof == 0) answer = my_itos(constant); else { if (cof == 1) answer = "x"; else answer = my_itos(cof) + "x"; if (constant > 0) { answer += " + " + my_itos(constant); } } cout << answer << endl; return answer; }
공백을 기준으로 토큰을 하였다면 거기에 나올 수 있는 경우의 수는 x 항, +, 상수항이 있다. +일 경우는 의미가 없다. 무조건 +만 나온다고 문제에서 제시가 되었기 때문이다. 문자열안에 x가 들어가 있다면 그 항의 계수가 1인지 아닌지를 비교한다음, 1이라면 cof++을 해주고 아니라면 x를 제외한 나머지를 stoi를 통해서 숫자로 변경시켜준다.
상수항의 경우는 해당 토큰된 문자가 모두 숫자로 변경되어야 하므로
constant += my_stoi(tokens[i]);
이렇게 진행시켜준다.
전체 코드 ⏬
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// string -> int로 바꿔줌
int my_stoi(string num)
{
int k = 1;
int n = 0;
for (int i = num.length() - 1; i >= 0; i--, k *= 10)
{
n += (num[i] - '0') * k;
}
return n;
}
// int -> string으로 바꿔줌
string my_itos(int n)
{
string num = "";
for (int i = n; i > 0; i /= 10)
{
char temp = (i % 10) + '0';
num = temp + num;
}
return num;
}
vector<string> tokenize(string letter)
{
vector<string> tokens;
int i, j;
for (i = 0; i < letter.length(); i = j + 1)
{
for (j = i; j < letter.length(); j++)
{
if (letter[j] == ' ')
{
tokens.push_back(letter.substr(i, j - i));
break;
}
}
if (j == letter.length())
{
tokens.push_back(letter.substr(i, j - i + 1));
}
}
return tokens;
}
string solution(string polynomial)
{
string answer = "";
int cof = 0;
int constant = 0;
vector<string> tokens = tokenize(polynomial);
for (int i = 0; i < tokens.size(); i++)
{
if (tokens[i] == "+")
{
}
// x에 대한 항인지를 물어보는 조건문 tokens[i]는 string인데 그 마지막 글자가 x인지 아닌지를 확인하고 싶다.
else if (tokens[i][tokens[i].length() - 1] == 'x')
{
if (tokens[i].length() == 1)
{
cof++;
}
else
{
cof += my_stoi(tokens[i].substr(0, tokens[i].length() - 1));
}
}
else
{
constant += my_stoi(tokens[i]);
}
}
// 출력 형식 맞추기
if (cof == 0)
answer = my_itos(constant);
else
{
if (cof == 1)
answer = "x";
else
answer = my_itos(cof) + "x";
if (constant > 0)
{
answer += " + " + my_itos(constant);
}
}
cout << answer << endl;
return answer;
}
과제 4-2
문제 설명
소인수분해란 어떤 수를 소수들의 곱으로 표현하는 것입니다. 예를 들어 12를 소인수 분해하면 2 _ 2 _ 3 으로 나타낼 수 있습니다. 따라서 12의 소인수는 2와 3입니다. 자연수 n이 매개변수로 주어질 때 n의 소인수를 오름차순으로 담은 배열을 return하도록 solution 함수를 완성해주세요.
제한사항
2 ≤ n ≤ 10,000
입출력 예
n | result |
---|---|
12 | [2, 3] |
17 | [17] |
420 | [2, 3, 5, 7] |
내가 제출한 코드
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> getSetVec(vector<int> factors)
{
factors.erase(unique(factors.begin(), factors.end()), factors.end());
return factors;
}
vector<int> solution(int n)
{
vector<int> answer;
vector<int> factors;
int i = 2;
while (n != 1)
{
if (n % i == 0)
{
n /= i;
factors.push_back(i);
i = 1;
}
i++;
}
sort(factors.begin(), factors.end());
answer = getSetVec(factors);
for (auto v : answer)
{
cout << v << " ";
}
return answer;
}
factors에 일단 소인수들을 넣는다. getSetVec을 통해서 벡터의 인자들 중 반복을 없앤다.
Set을 만드는 방법은 sort, unique, erase를 사용한다.
- unique 함수의 경우 연속된 중복 원소는 vector 제일 뒷 부분으로 쓰레기값으로 보내진다. 그렇기 때문에 쓰레기값을 erase시킨 것이다.
- erase(지울 원소 자리, 지울 원소 자리 - 1) : 구간을 지우는 방법. unique 함수는 vector 배열에서 중복되지 않는 원소들을 앞에서부터 채워나간다. 그리고 vector 내부를 바꾸어주고 자신이 바꾼 vector의 end() 부분을 반환, 즉, 바뀌지 않은 시작 부분을 주소로 반환하게 된다.
교수님 코드
#include <iostream>
#include <vector>
#include <string>
using namespace std;
vector<int> solution(int n)
{
vector<int> answer;
for (int i = 1; i <= n; i++)
{
if (n % i == 0)
{
answer.push_back(i);
// 이걸 넣어주지 않으면 모든 약수가 들어간다. 소인수가 들어가는게 아니라. 나누어 떨어지지 않으 때까지 같은 수로 나눈다. 그렇게 소인수 분해를 한다. 그거를 그대로 따라한 것이다.
while (n % i == 0)
{
n = n / i;
}
}
}
return answer;
}
졸 간단,,,,여기서는 왜 집합이 필요 없어? 애초에 한번 인자로 나누게 되면 그 다음 같은 수로 안나누어질때까지 나누기만 하지, answer에 push_back을 하지는 않으므로!
- n을 i로 나눌 수 있을 때까지 나눈다. 졸라 핵심
댓글남기기