수식을 계산할 때 곱셈과 덧셈 중 곱셈을 먼저하는 등, 우선순위가 있는 기호들이 있다는 건 기본이죠.그런데 그것을 알고리즘으로 구현하려고 하니까, 쉽게 되지 않고 코드만 복잡해지고 결과는 실패하는 일이 많았습니다.
그때 떠오른 것이 바로 자료구조!!
자료구조 수업을 들었던 게 몇 년 전인데 신기하게 떠오르더군요. 자료구조에서 후위표기식이라는 것이 있는데, 그것을 적용하여 입력을 계산하니 옳바른 결과가 나왔습니다.
후위표기식에 대해서 더 자세히 알아보시려면 아래를 참고하세요.
https://jamanbbo.tistory.com/54
코드
CalculateHelper.java
package com.example.calculator;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;
//https://jamanbbo.tistory.com/54의 소스코드를 참조, 수정하였음
public class CalculateHelper {
public static double num1;
public static double num2;
public static double resultNumber;
//사용자의 input을 각각 구분하여 ArrayList에 저장하는 메소드
private ArrayList splitTokens(String equation) {
String[] constant = equation.split(" "); //공백을 기준
ArrayList constantList = new ArrayList();
double number = 0;
boolean flag = false;
for (String data : constant) {
if (data.equals(" ")) {
continue;
}
if (checkNumber(data)) {
number = number * 10 + Double.parseDouble(data);
flag = true;
} else {
if (flag) {
constantList.add(number);
number = 0;
}
flag = false;
constantList.add(data);
}
}
if (flag) {
constantList.add(number);
}
return constantList;
}
//후위 표기식으로 변형
private ArrayList infixToPostfix(ArrayList constant) {
ArrayList result = new ArrayList();
HashMap level = new HashMap();
Stack stack = new Stack();
//각 기호의 우선순위 레벨. 곱하기, 나누기 > 더하기, 빼기 > 기타
level.put("*", 3);
level.put("/", 3);
level.put("+", 2);
level.put("-", 2);
level.put("(", 1);
for (Object object : constant) {
if (object.equals("(")) {
stack.push(object);
} else if (object.equals(")")) {
while (!stack.peek().equals("(")) {
Object val = stack.pop();
if (!val.equals("(")) {
result.add(val);
}
}
stack.pop();
} else if (level.containsKey(object)) {
if (stack.isEmpty()) {
stack.push(object);
} else {
if (Double.parseDouble(level.get(stack.peek()).toString()) >= Double.parseDouble(level.get(object).toString())) {
result.add(stack.pop());
stack.push(object);
} else {
stack.push(object);
}
}
} else {
result.add(object);
}
}
while (!stack.isEmpty()) {
result.add(stack.pop());
}
return result;
}
//후위 표기식을 계산
private Double postFixEval(ArrayList expr) {
Stack numberStack = new Stack();
for (Object o : expr) {
if (o instanceof Double) {
numberStack.push(o);
} else if (o.equals("+")) {
num1 = (Double) numberStack.pop();
num2 = (Double) numberStack.pop();
numberStack.push(num2 + num1);
} else if (o.equals("-")) {
num1 = (Double) numberStack.pop();
num2 = (Double) numberStack.pop();
numberStack.push(num2 - num1);
} else if (o.equals("*")) {
num1 = (Double) numberStack.pop();
num2 = (Double) numberStack.pop();
numberStack.push(num2 * num1);
} else if (o.equals("/")) {
num1 = (Double) numberStack.pop();
num2 = (Double) numberStack.pop();
numberStack.push(num2 / num1);
}
}
resultNumber = (Double) numberStack.pop();
return resultNumber;
}
public Double process(String equation) {
ArrayList postfix = infixToPostfix(splitTokens(equation));
Double result = postFixEval(postfix);
return result;
}
public boolean checkNumber(String str) {
char check;
if (str.equals(""))
return false;
for (int i = 0; i < str.length(); i++) {
check = str.charAt(i);
if (check < 48 || check > 58) {
if (check != '.')
return false;
}
}
return true;
}
}
설명
각 메소드에 대한 것은 주석을 읽어주세요!
크게 복잡한 것은 없습니다. MainActivity에서 사용자의 입력이 기록된 textView의 텍스트를 CalculateHelper로 전달하면, CalculateHelper에서는 그것을 후위표기식으로 변환 후 계산까지 하고 MainActivity로 리턴합니다.
궁금하신 점이 있으시다면 댓글로 남겨주세요.
xml코드
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1.5"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
<TextView
android:id="@+id/first_textView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:text=""
android:textSize="30dp" />
</ScrollView>
<TextView
android:id="@+id/second_textView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:layout_weight="2"
android:text=""
android:textSize="30dp" />
</LinearLayout>
<GridLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:columnCount="4"
android:orientation="horizontal"
android:rowCount="5">
<Button
android:id="@+id/clear"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="C" />
<Button
android:id="@+id/bracket"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="( )" />
<Button
android:id="@+id/percent"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="%" />
<Button
android:id="@+id/div"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="÷" />
<Button
android:id="@+id/num7"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="7" />
<Button
android:id="@+id/num8"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="8" />
<Button
android:id="@+id/num9"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="9" />
<Button
android:id="@+id/mul"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="X" />
<Button
android:id="@+id/num4"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="4" />
<Button
android:id="@+id/num5"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="5" />
<Button
android:id="@+id/num6"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="6" />
<Button
android:id="@+id/sub"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="-" />
<Button
android:id="@+id/num1"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="1" />
<Button
android:id="@+id/num2"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="2" />
<Button
android:id="@+id/num3"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="3" />
<Button
android:id="@+id/add"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="+" />
<Button
android:id="@+id/back"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="back" />
<Button
android:id="@+id/num0"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="0" />
<Button
android:id="@+id/dot"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="." />
<Button
android:id="@+id/equal"
android:layout_rowWeight="1"
android:layout_columnWeight="1"
android:layout_margin="-5dp"
android:text="=" />
</GridLayout>
</LinearLayout>
'Android Programming(java)' 카테고리의 다른 글
로그인과 회원가입 (1) | 2020.02.20 |
---|---|
opencv를 이용한 사이즈 측정 앱(2) (0) | 2020.02.20 |
opencv를 이용한 사이즈 측정 앱(1) (0) | 2020.02.19 |
간단한 계산기 앱 만들기(Android, 자료구조-후위표기식)(1) (0) | 2020.02.18 |