다항식 계산기 만들기를 계속 한다!
일단 테스트 케이스 ((20 + 20)) + 20 == 60을 해결하는데 강사님이 써주신 코드는 이렇다.
import java.util.Arrays;
import java.util.stream.Collectors;
public class Calc {
public static int run(String exp) {
// (20 + 20) + 20
// 괄호 제거
exp = stripOuterBrackets(exp);
// 단일항이 들어오면 바로 리턴
if (!exp.contains(" ")) {
return Integer.parseInt(exp);
}
boolean needToMulti = exp.contains(" * ");
boolean needToPlus = exp.contains(" + ") || exp.contains(" - ");
boolean needToSplit = exp.contains("(") || exp.contains(")");
boolean needToCompound = needToMulti && needToPlus;
if (needToSplit) {
int bracketsCount = 0;
int splitPointIndex = -1;
for (int i = 0; i < exp.length(); i++) {
if (exp.charAt(i) == '(') {
bracketsCount++;
} else if (exp.charAt(i) == ')') {
bracketsCount--;
}
if (bracketsCount == 0) {
splitPointIndex = i;
break;
}
}
String firstExp = exp.substring(0, splitPointIndex + 1);
String secondExp = exp.substring(splitPointIndex + 4);
return Calc.run(firstExp) + Calc.run(secondExp);
} else if (needToCompound) {
String[] bits = exp.split(" \\+ ");
String newExp = Arrays.stream(bits)
.mapToInt(Calc::run)
.mapToObj(e -> e + "")
.collect(Collectors.joining(" + "));
return run(newExp);
}
if (needToPlus) {
exp = exp.replaceAll("- ", "+ -");
String[] bits = exp.split(" \\+ ");
int sum = 0;
for (int i = 0; i < bits.length; i++) {
sum += Integer.parseInt(bits[i]);
}
return sum;
} else if (needToMulti) {
String[] bits = exp.split(" \\* ");
int sum = 1;
for (int i = 0; i < bits.length; i++) {
sum *= Integer.parseInt(bits[i]);
}
return sum;
}
throw new RuntimeException("해석 불가 : 올바른 계산식이 아니야");
}
private static String stripOuterBrackets(String exp) {
int outerBracketsCount = 0;
while (exp.charAt(outerBracketsCount) == '(' && exp.charAt(exp.length() - 1 - outerBracketsCount) == ')') {
outerBracketsCount++;
}
if (outerBracketsCount == 0) return exp;
return exp.substring(outerBracketsCount, exp.length() - outerBracketsCount);
}
}
나는 어제 밤에 stripOuterBrakets 메서드를 for문으로 썼었는데 가독성이 떨어지는 것 같아서 강사님 코드로 바꿨다. 또 Calc 메서드 안에서 괄호를 우선적으로 처리하는 코드를 쓰고 있었던 점 생각하고 읽어보자.
여튼간에 다음 테스트 케이스는 (10 + 20) * 3 인데 강사님 코드 같은 경우엔 더하기만 존재하기 때문에 곱셈연산자를 무시하고 더해버린다.
(일단 수정하는 쪽만 쓰겠다.)
if (needToSplit) {
int brackersCount = 0;
int splitPointIndex = -1;
for (int i = 0; i < exp.length(); i++) {
if (exp.charAt(i) == '(') brackersCount++;
else if (exp.charAt(i) == ')') brackersCount--;
if (brackersCount == 0) {
splitPointIndex = i;
break;
}
}
String firstExp = exp.substring(0, splitPointIndex + 1);
String secondExp = exp.substring(splitPointIndex + 4);
char operator = exp.charAt(splitPointIndex + 2);
exp = Calc.run(firstExp) + " " + operator + " " + Calc.run(secondExp);
return Calc.run(exp);
}
그래서 splitPointInex 변수를 활용해서 자르는 포인트를 알고 있으니 그 부분을 찾고 +2는 연산자를 정확히 집을 수 있는 char 변수가 된다. 그걸 operator에 넣고 exp를 다시 올바른 수식으로 선언해주고 return을 run(exp)로 돌려서 올바른 값을 찾도록 하셨다.
나는 별개로 코드를 짰었기 때문에 수정해야할 것들이 있었다. 일단 ()의 연산은 제대로 되는데 겹괄호에서 문제가 생긴 것이 제일 컸다.
if (exp.contains("(")) { //괄호가 있다면 실행.
int openBracket = exp.indexOf("("); // 괄호 위치 인덱스 번호로 찾기.
int closeBracket = exp.indexOf(")"); // 올바른 카운트를 위해서 openBracket 과 같은 수로 지정.
int count = 0; //괄호 개수 세기
for (int i = openBracket; i < exp.length(); i++) { //인덱스 길이만큼 반복하면서 괄호 찾기
if (exp.charAt(i) == '(') count++; //괄호 '(' 추적후 세기.
else if (exp.charAt(i) == ')') count--; // ')'가 시작되면 카운트 감소
if (count == 0) { // '(' 와 ')'의 개수가 같아야 식이 성립하기 때문에 0이될 때를 조건으로 삼음.
closeBracket = i; // 반복문 반복 횟수가 괄호 마지막 인덱스이기 때문에 그 값을 closeBraket에 넣어줌.
break; //브레이크
}
}
String expression = exp.substring(openBracket + 1, closeBracket); //첫괄호와 끝괄호 위치를 걷어냄.
int expResult = run(expression); // run메서드 전달 실행.
exp = exp.substring(0, openBracket) + expResult + exp.substring(closeBracket + 1);
//여는 괄호 이전의 수식 + 괄호 수식 결과값 + 닫는 괄호 이후의 수식
return run(exp); // run메서드 전달 실행.
}
수정전 코드에는 반복문이 없었다.
if (exp.contains("(")) {
int openBracket = exp.indexOf("(");
int closeBracket = exp.indexOf(")");
String expression = exp.substring(openBracket + 1, closeBracket);
int expResult = run(expression);
exp = exp.substring(0, openBracket) + expResult + exp.substring(closeBracket + 1);
return run(exp);
}
코드를 제대로 이해하지 못해서 일어난 참사인데, 아마 어제 밤에는 내가 밑의 메서드에서 반복 괄호를 없애줄거라고 착각을 하고 있었던 것 같다. 여하튼 위 코드에서 그 위 코드로 수정해서 일단 내주신 모든 테스트 케이스는 통과가 된다.
public class Calc {
public static int run(String exp) {
exp = exp.trim();
exp = stripOuterBrackets(exp);
// 단일항이 들어오면 바로 리턴
if (!exp.contains(" ")) {
return Integer.parseInt(exp);
}
boolean needToMulti = exp.contains(" * ");
boolean needToSum = exp.contains(" + ") || exp.contains(" - ");
boolean needToSplit = exp.contains("(") || exp.contains(")");
boolean needToCompound = needToSum && needToMulti;
// if (exp.contains("(")) { //괄호가 있다면 실행.
// int openBracket = exp.indexOf("("); // 괄호 위치 인덱스 번호로 찾기.
// int closeBracket = exp.indexOf(")");
// int count = 0; //괄호 개수 세기
// for (int i = openBracket; i < exp.length(); i++) { //인덱스 길이만큼 반복하면서 괄호 찾기
// if (exp.charAt(i) == '(') count++; //괄호 '(' 추적후 세기.
// else if (exp.charAt(i) == ')') count--; // ')'가 시작되면 카운트 감소
// if (count == 0) { // '(' 와 ')'의 개수가 같아야 식이 성립하기 때문에 0이될 때를 조건으로 삼음.
// closeBracket = i; // 반복문 반복 횟수가 괄호 마지막 인덱스이기 때문에 그 값을 closeBraket에 넣어줌.
// break; //브레이크
// }
// }
// String expression = exp.substring(openBracket + 1, closeBracket); //첫괄호와 끝괄호 위치를 걷어냄.
// int expResult = run(expression); // run메서드 전달 실행.
// exp = exp.substring(0, openBracket) + expResult + exp.substring(closeBracket + 1);
// //여는 괄호 이전의 수식 + 괄호 수식 결과값 + 닫는 괄호 이후의 수식
// return run(exp); // run메서드 전달 실행.
// }
// 강사님 코드
if (needToSplit) {
int splitPointIndex = findSplitPointIndex(exp);
String firstExp = exp.substring(0, splitPointIndex);
String secondExp = exp.substring(splitPointIndex + 1);
char operator = exp.charAt(splitPointIndex);
exp = Calc.run(firstExp) + " " + operator + " " + Calc.run(secondExp);
return Calc.run(exp);
}
int sum = 0;
int mult = 1;
int rs = 0;
if (needToCompound) {
String[] bits = exp.split(" \\+ ");
char multi = '*';
for (int i = 0; i < bits.length; i++) {
if (bits[i].indexOf(multi) != -1) {
rs += run(bits[i]);
}
}
for (int i = 0; i < bits.length; i++) {
if (bits[i].indexOf(multi) == -1) {
sum += Integer.parseInt(bits[i]);
}
}
return sum + rs;
} else if (needToSum) {
exp = exp.replace("- ", "+ -");
String[] bits = exp.split(" \\+ ");
for (String bit : bits) {
sum += Integer.parseInt(bit);
}
return sum;
} else if (needToMulti) {
String[] bits = exp.split(" \\* ");
for (String bit : bits) {
mult *= Integer.parseInt(bit);
}
return mult;
}
return sum + mult;
// throw new RuntimeException("해석불가 : 올바른 식이 필요해.");
}
private static int findSplitPointIndex(String exp) {
int index = findSplitPointIndexBy(exp, '+');
if (index >= 0) return index;
return findSplitPointIndexBy(exp, '*');
}
private static int findSplitPointIndexBy(String exp, char findChar) {
int brackesCount = 0;
for (int i = 0; i < exp.length(); i++) {
char c = exp.charAt(i);
if (c == '(') {
brackesCount++;
} else if (c == ')') {
brackesCount--;
} else if (c == findChar) {
if (brackesCount == 0) return i;
}
}
return -1;
}
private static String stripOuterBrackets(String exp) {
int outerBracketsCount = 0;
while (exp.charAt(outerBracketsCount) == '(' && exp.charAt(exp.length() - 1 - outerBracketsCount) == ')') {
outerBracketsCount++;
}
if (outerBracketsCount == 0) return exp;
return exp.substring(outerBracketsCount, exp.length() - outerBracketsCount);
}
}
강사님이 코드를 수정하셨다. 한번 수정한 부분문 살펴보면,
if (needToSplit) {
int splitPointIndex = findSplitPointIndex(exp);
// 1. find 메서드 실행. // 리턴 된 i 값 -> 전달 받음
String firstExp = exp.substring(0, splitPointIndex);
// 자르기 -> 첫번째 값
String secondExp = exp.substring(splitPointIndex + 1);
// 자르기 -> 두번째 값
char operator = exp.charAt(splitPointIndex); // 연산자 인덱스 위치 저장
exp = Calc.run(firstExp) + " " + operator + " " + Calc.run(secondExp);
// 첫번째 값(메서드 실행으로 구하기) + 연산자 + 두번째 값(메서드 실행으로 구하기)
return Calc.run(exp);
}
private static int findSplitPointIndex(String exp) {
int index = findSplitPointIndexBy(exp, '+');
// 2. find By 메서드 실행. +를 찾기
if (index >= 0) return index; // 리턴 받은 i 값이 참이면 리턴
// + 가 맞다면 리턴
return findSplitPointIndexBy(exp, '*'); // 아니면 * 로 리턴
}
private static int findSplitPointIndexBy(String exp, char findChar) {
int brackesCount = 0;
for (int i = 0; i < exp.length(); i++) { // 3. 반복문을 통해서 +나 * 추적
char c = exp.charAt(i); // 인덱스 안의 배열의 i번재 문자 선언
if (c == '(') { //검증중
brackesCount++; //찾으면 카운트
} else if (c == ')') { //(가 없을 때 ) 찾으면 카운트 --
brackesCount--;
} else if (c == findChar) { // 두 경우 다 아닐 때 c가 findChar = ( + or * )와 같을 때 접근
if (brackesCount == 0) return i; // c == findChar가 참일 경우 i 값 리턴
}
}
return -1; //모두 아닐경우 -1 리턴
}
//괄호를 만나기 전의 연산자냐 괄호 안의 연산자냐를 구별하는 함수.
메서드를 두개 더 만드셨고 활용하고 있다. '(' , ')' 위치를 찾기 위한 코드다.
코드 작동은 각주로 코드에 달아두었다.
덤으로 내가 진행하고 있는 코드는 지금 강사님이 하신 것 보다 앞서 있는데 최종적인 목표에서 stackOverflow에 걸려서 해결을 못하고 있다. compound에서 무한 반복중인데 뭘 써야 좋을지 고민이다. 내 코드는 아직 위에 있는 코드와 동일하다.
아마 순차적으로 하나씩 문제를 해결해 나가면서 풀어야하는 것을 극단적으로 보여주기 위해서 강사님은 하나하나 구현하고 계신 것 같다.
난 괄호를 없애는 것 자체에 집중해서 결과적으로 강사님이 내주시는 테스트 케이스가 최종 목표 전까지는 다 실행이 잘 된다. 아마 우연일 것이다.
아 해결 했다. 디버그를 통해서 뭐가 문제인지 계속 확인 했는데, 일단 테스트 케이스 두개를 보고 시작하자.
@Test
@DisplayName("1 * 1 + ( 1 + (1 * (1 - 1))) == 2")
void t24() {
assertThat(Calc.run("1 * 1 + ( 1 - (1 * 1 - (1 - 1)))")).isEqualTo(1);
}
@Test
@DisplayName("3 * 1 + (1 - (4 * 1 - (1 - 1))) == 0")
void t50() {
assertThat(Calc.run("3 * 1 + (1 - (4 * 1 - (1 - 1)))")).isEqualTo(0);
}
내가 최종적으로 통과해야 할 테스트 케이스는 이거였고 문제점은 needToCompound 에서 "1 x 1 - 0", "4 x 1 - 0" 을 전달 받고 무한 반복 스택오버플로우가 걸리는 것이였다. 코드 한줄만 추가했더니 해결 됐다.
exp = exp.replace("- ", "+ -");
심지어 예전에 사용했다. 엄청 간단하게 해결 되니까 맥이 풀렸다.. 이제 강사님 코드로 해결을 순차적으로 하면 될 것 같다.
지금 강사님이 진행하신 코드에서는 테스트 케이스 -(10 + 5) == -15 에서 막혀있고 이 코드의 문제점은 "-(" 이 부분이 문제다. -( 부분을 -1 * ( 이렇게 바꿀 수 있다면 좋을텐데 그 방법을 몰라서 헤메고 있다.
고민 중에 강사님이 푸셨다.
//Calc 메서드 상단에 위치
if (isCaseMinusBracket(exp)) exp = exp.substring(1) + "* -1";
private static boolean isCaseMinusBracket(String exp) {
// -( 로 시작하는지?
if (exp.startsWith("-(") == false) return false;
// 괄호로 감싸져 있는지?
int bracketsCount = 0;
for (int i = 0; i < exp.length(); i++) {
char c = exp.charAt(i);
if (c == '(') {
bracketsCount++;
} else if (c == ')') {
bracketsCount--;
}
if (bracketsCount == 0) {
if (exp.length() - 1 == i) return true;
}
}
return false;
}
"-1 * (" 로 바꾸는게 아니고 '(' 는 빠진다.
아무튼 이런 식으로 코드를 추가하면 -(10 + 5) == -15 테스트 케이스는 통과된다.
밑의 새로운 메서드는 검증 과정을 담은 코드인데 메서드 안의 for문은 없어도 작동된다. (테스트케이스 -(10 + 5) == -15까지만)
강사님도 쓰시면서 사족이라고 하셨는데 그래도 살펴보자면 저 for문은 결국 괄호를 확인하면서 검증 단계를 거치는 것과 똑같은 코드이다. 변수 몇가지만 수정했다.
아무튼 이렇게 수업이 끝이 났다. 문제를 다 풀긴 했는데 강사님이 제시하는 코드대로 따라가는게 생각보다 어려운 것 같다.
'Coding History' 카테고리의 다른 글
레드벨벳 dumb dumb, 덤을 몇번 불렀을까? (코딩 심심풀이) (0) | 2024.06.27 |
---|---|
2024. 06. 26 문제풀이 (0) | 2024.06.26 |
2024. 06. 25 문제풀이 (0) | 2024.06.25 |
국비 지원 IT(웹앱개발) 취업반 강의 14일차(다항식 계산기 만들기 과정) (0) | 2024.06.25 |
2024. 06. 24 문제풀이 (0) | 2024.06.24 |