- 후위 표기법을 활용한 계산기 만들기
이번에는 Java 콘솔로 계산기를 만들어보려고 한다.
(그냥 사칙연산만 되는 계산기가 아니라 괄호의 우선순위도 고려한 계산기다.)
괄호의 우선순위가 적용된 계산기를 만들기 위해서는 '후위 표기법'에 대한 개념이 있어야 했다.
로직을 만들어보기 위해 백준 문제들을 먼저 풀어보았다.
괄호의 우선순위를 적용하기 위해서는 일반식 -> 후위 표기법 -> 사칙연산 계산의 순서로 진행해야 한다.
Stack을 활용하여 후위 표기법으로 변환할 것이며, Stack에 담을 때 연산자의 우선순위를 지정해주어야 한다.
// 후위표현식으로 변환
private String convertPostfix(String formula) {
Stack<Character> stack = new Stack<>();
StringBuilder num = new StringBuilder();
StringBuilder postfix = new StringBuilder();
char[] chars = formula.replace(" ", "").toCharArray();
for(int i = 0 ; i < chars.length ; i++) {
char c = chars[i];
int priority = priority(c);
if(priority > 0) {
if(!num.isEmpty()) {
postfix.append(num).append(" ");
num.setLength(0);
}
switch (c) {
case '*':
case '/':
case '+':
case '-':
while(!stack.isEmpty() && stack.peek() != '(' && priority(stack.peek()) >= priority) {
postfix.append(stack.pop()).append(" ");
}
stack.push(c);
break;
case '(':
stack.push(c);
break;
case ')':
while(!stack.isEmpty() && stack.peek() != '(') {
postfix.append(stack.pop()).append(" ");
}
stack.pop(); // '(' 제거
break;
}
} else {
num.append(c);
}
}
// 마지막 숫자
if(!num.isEmpty()) {
postfix.append(num).append(" ");
}
// stack에 있는 연산자
while(!stack.isEmpty()) {
postfix.append(stack.pop()).append(" ");
}
return postfix.toString().trim();
}
// 연산자 우선순위 정하기
private int priority(char c) {
switch (c) {
case '(':
case ')': return 3;
case '*':
case '/': return 2;
case '+':
case '-': return 1;
default : return 0;
}
}
후위 표기법으로 변환이 완료되었다면 이제 사칙연산으로 계산해야 한다.
계산을 할 때는 해당 문자가 숫자인지 연산자인지 체크하는 메서드도 필요하다.
// 후위표현식 계산
private double calcPostfix(String postFix) {
Stack<Double> stack = new Stack<>();
String[] formulas = postFix.split(" ");
for(int i = 0 ; i < formulas.length ; i++) {
String formula = formulas[i];
if(checkedNumber(formula)) {
stack.push(Double.parseDouble(formula));
} else {
double o2 = Double.parseDouble(stack.pop().toString());
double o1 = Double.parseDouble(stack.pop().toString());
switch (formula) {
case "+":
stack.push(o1 + o2);
break;
case "-":
stack.push(o1 - o2);
break;
case "*":
stack.push(o1 * o2);
break;
case "/":
stack.push(o1 / o2);
break;
}
}
}
return stack.pop();
}
// 숫자인지 연산자인지 체크
private boolean checkedNumber(String num) {
try {
Double.parseDouble(num);
return true;
} catch (NumberFormatException e) {
return false;
}
}
이제 메서드는 다 만들어졌다.
main에서 출력하면 아래와 같이 계산이 되어 나온다.
public static void main(String[] args) throws IOException {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String formula = br.readLine();
PostfixCalc calc = new PostfixCalc();
String postFix = calc.convertPostfix(formula);
Double result = calc.calcPostfix(postFix);
System.out.println("|=======================================================|");
System.out.printf(" 계 산 결 과 > %.2f%n", result);
System.out.println(" 후 위 표 현 식 > " + postFix);
System.out.println("|=======================================================|");
} catch (Exception e) {
System.err.println("계산식을 확인해주세요.");
}
}
이번에는 Java 콘솔로만 출력되는 계산기를 만들었는데, 해당 로직 활용하여 Vue.js나 React로 계산기 모양으로 만들고 웹이나 앱으로 확장시키는 것도 좋을 거 같다는 생각이 들었다, 끗.
- PostfixCalc.java
package toy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Stack;
public class PostfixCalc {
public static void main(String[] args) throws IOException {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String formula = br.readLine();
PostfixCalc calc = new PostfixCalc();
String postFix = calc.convertPostfix(formula);
Double result = calc.calcPostfix(postFix);
System.out.println("|=======================================================|");
System.out.printf(" 계 산 결 과 > %.2f%n", result);
System.out.println(" 후 위 표 현 식 > " + postFix);
System.out.println("|=======================================================|");
} catch (Exception e) {
System.err.println("계산식을 확인해주세요.");
}
}
// 후위표현식으로 변환
private String convertPostfix(String formula) {
Stack<Character> stack = new Stack<>();
StringBuilder num = new StringBuilder();
StringBuilder postfix = new StringBuilder();
char[] chars = formula.replace(" ", "").toCharArray();
for(int i = 0 ; i < chars.length ; i++) {
char c = chars[i];
int priority = priority(c);
if(priority > 0) {
if(!num.isEmpty()) {
postfix.append(num).append(" ");
num.setLength(0);
}
switch (c) {
case '*':
case '/':
case '+':
case '-':
while(!stack.isEmpty() && stack.peek() != '(' && priority(stack.peek()) >= priority) {
postfix.append(stack.pop()).append(" ");
}
stack.push(c);
break;
case '(':
stack.push(c);
break;
case ')':
while(!stack.isEmpty() && stack.peek() != '(') {
postfix.append(stack.pop()).append(" ");
}
stack.pop(); // '(' 제거
break;
}
} else {
num.append(c);
}
}
// 마지막 숫자
if(!num.isEmpty()) {
postfix.append(num).append(" ");
}
// stack에 있는 연산자
while(!stack.isEmpty()) {
postfix.append(stack.pop()).append(" ");
}
return postfix.toString().trim();
}
// 연산자 우선순위 정하기
private int priority(char c) {
switch (c) {
case '(':
case ')': return 3;
case '*':
case '/': return 2;
case '+':
case '-': return 1;
default : return 0;
}
}
// 후위표현식 계산
private double calcPostfix(String postFix) {
Stack<Double> stack = new Stack<>();
String[] formulas = postFix.split(" ");
for(int i = 0 ; i < formulas.length ; i++) {
String formula = formulas[i];
if(checkedNumber(formula)) {
stack.push(Double.parseDouble(formula));
} else {
double o2 = Double.parseDouble(stack.pop().toString());
double o1 = Double.parseDouble(stack.pop().toString());
switch (formula) {
case "+":
stack.push(o1 + o2);
break;
case "-":
stack.push(o1 - o2);
break;
case "*":
stack.push(o1 * o2);
break;
case "/":
stack.push(o1 / o2);
break;
}
}
}
return stack.pop();
}
// 숫자인지 연산자인지 체크
private boolean checkedNumber(String num) {
try {
Double.parseDouble(num);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}