텍스트## 1. 추가 구현 목표
기존의 구현했던 프로그램은 첫 번째 값, 두 번째 값, 연산자를 입력하면 연산 후 연산 결과를 출력하는 기능이었지만, 세 개 이상의 값을 넣어도 연산 후 결과 값을 출력하도록 추가로 구현할 것이고 또 하나로는 LEVEL 3를 구현하는 것이다.
AddOperation(더하기), SubtractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스들을 AbstractOperation라는 클래스명으로 만들어 사용하여 추상화하고 Calculator 클래스의 내부 코드를 변경하기
//추상화된 Operation 생성
abstract class AbstractOperation {
abstract fun perform(num1:Double, num2:Double):Double
}
// 더하기 오버라이딩
class AddOperation : AbstractOperation() {
override fun perform(num1:Double, num2:Double):Double {
return num1 + num2 // num1.plus(num2)도 가능
}
추상화된 Operation 클래스를 생성하고 이를 이용해서 더하기, 빼기, 곱하기, 나누기 연산기능을 각 오버라이딩 해줍니다.
+, -, *, / 의 짝을 통해서 연산이 이루어져야 하므로 짝 형태인 Map 키워드를 이용해 주면 됩니다 ! calculator 클래스의 내부 코드를 변경하겠습니다.
// class Calculator
var operations : Map <String, AbstractOperation> = mapOf(
"+" to AddOperation(),
"-" to SubstractOperation(),
"*" to MultiplyOperation(),
"/" to DivideOperation()
}
이제 이렇게 짝을 이루어주었기 때문에 main fun에서도 코드의 변경이 필요합니다.
// 기존 코드
val result = cal.operate(num1.toDouble(), num2.toDouble(), operator)
println("결과 : ${result}")
// 수정 코드
val operation = cal.operation[operator]
if (operation == null) {
println("지원되지 않는 연산자입니다.\n")
}
else {
val result = operation.perform(num1, num2)
if(result != null)
println("결과 : $result")
println("====================================\n")
}
세 개의 값을 연산할 때, 예를 들어 10 + 3 * 2 - 7일 경우에 먼저 계산해야 하는 값은 괄호를 사용해야 하지만 후위 표기법은 괄호를 사용하지 않고도 연산이 가능합니다.
후위 표기법은 연산자를 피연산자 뒤에 표기하는 방법으로 스택을 사용할 때 가장 빈번하게 등장하는 것들 중 하나입니다.
기본 연산 방법은 중위 표기법인 A+B 입니다. 그렇기 때문에 중위 표기에서 후위 표기로 변환하는 방법에 대해서 간단히 정리해보겠습니다.
(알고리즘 공부할 때 배웠던걸 여기서 쓰게 되니 되게 신기한..)
중위 표기법 ➡️ 후위 표기법
[ A*B - C/D 변환 ]
1. 각 연산자에 대해 우선순위에 따라 괄호를 사용해 표현해준다.
((AB) - (C/D))
2. 각 연산자를 대응하는 오른쪽 괄호의 뒤로 이동
((AB)(CD)/)-
3. 괄호를 제거한다.
AB*CD/-
전위 표기법은 왼쪽 괄호의 앞으로 이동시키면 된다 !
스택과 같이 쓰이기 때문에 스택까지 수식을 연산하면 어떻게 되는지 한 번 정리해보자.
코틀린에서도 스택은 push와 pop을 이용해주면 된다.

(2) 연산자 우선순위
*,/,^,%+,-(
(3) 중위표기법 ➡️ 후위표기법 (코드)
- 피연산자는 스택에 넣지않고 그대로 출력
- 연산자
- 스택이 비어있을경우
push- 스택이 비어있지 않을경우 스택의 연산자와 우선순위를 비교,
stack top 연산자가 현재 연산자의 우선순위보다 낮을때 까지 pop 후 출력
현재연산자 stack push- 왼쪽 괄호
(를 만나면 stack push- 오른쪽 괄호
)를 만나면 왼쪽 괄호(가 나올때까지 모든 연산자 pop 후 출력 ->(삭제- 표기식에 문자가 남지 않았다면 stack 을 비운다 pop 후 출력
스택 기능💫
- Stack 선언 : Stack()로 선언
- Stack 값 삽입 : push() 이용
- Stack 마지막 값 삭제 : pop() 이용
- Stack 마지막 값 확인 및 값 반환 : peek() 이용
값이 비어있을 때 사용시 에러 발생- Stack 크기 확인 : size() 이용
- Stack 비었는지 확인 : isEmpty(), isNotEmpty() 이용
후위표기법으로 코드를 재설정하게 되면 main 기능도 변경해줘야 해서 추상화 클래스 부분을 뺀 나머지는 갈아엎어야...합니다.... 그래서 그냥 새로 AdditionalCal이라는 파일을 만들어서 구현하도록 하겠습니다. 추상 클래스로 구현하는 부분은 동일합니다.
convert 함수는 AddCalculator (Calculator과 동일) 클래스 안에 구현해줍니다.
var postfix = mutableListOf<String>() // 후위표기법(postfix)를 담을 리스트
var stack = Stack<String>() // 연산자 저장할 스택
var precedence = mapOf("+" to 1, "-" to 1, "*" to 2, "/" to 2) //연산자 우선순위
var currentNum = "" // 현재 읽고 있는 숫자 저장
for(token in infix) { // 중위표기법에 있는 수식을 문자 하나씩 처리
if(token.isDigit() || token == '.') // 문자가 숫자이거나 소수점이면
currentNum += token // 현재 숫자 생성
} else {
if(currentNum.isNotEmpty()) { // 현재 숫자가 존재한다면
postfix.add(currentNum) // 후위 표기법 리스트에 추가
currentNum = "" // 현재 숫자 초기화
}
// 현재 문자가 맵에 정의된 연산자가 맞다면
if(token.toString() in precedence.keys) {
// 스택 맨 위에 있는 연산자와 현재 연산자의 우선순위 비교 후
스택에 있는 연산자를 후위표기법 리스트에 추가
while(stack.isNotEmpty() && stack.peek() in precedence.keys &&
precedence[token.toString()]!! <= precedence[stack.peek()]!!) {
// 비교를 위해 pop을 이용해 stack에서 빼내기
profix.add(stack.pop())
}
stack.push(token.toString()) // 스택에 현재 연산자 추가
}
}
}
if(currentNum.isNotEmpty()) {
postfix.add(currentNum)
}
while(stack.isNotEmpty()) {
postfix.add(stack.pop())
} }
}
>```
즉, convert 함수는 입력받은 수식(중위 표기법)을 후위 표기법으로 변환하기 위해 사용하고, 입력된 중위 표기법 수식을 숫자 및 소수점과 연산자를 기준으로 토큰으로 분리하고, 스택을 사용해서 후위 표기법으로 변환하는 과정을 가집니다.
fun String.isNumeric() : Boolean {
return matches("^-?\\d+(\\.\\d+)?\$".toRegex())
}
isNumeric 함수는 정규표현식을 사용하여 주어진 문자열이 숫자인지를 판단해줍니다. toRegex는 String을 정규표현식으로 확장하는 함수입니다! 정규표현식을 사용하는 이유는 정규표현식 객체를 사용했을 때 문자열을 패턴과 비교하거나 원하는 패턴을 검색할 수 있기 때문입니다.
- ? : 음수 기호 - 가 있거나 없는 경우 (옵션)
- \d+ : 숫자 하나 이상이 있는 경우
- (.\d+)? : 소수점과 숫자 하나 이상 있을 경우 (옵션)
calculate 함수는 후위 표기법으로 변환된 수식을 입력받아 실제로 계산을 수행하는 기능을 가지고 있습니다. 이 함수는 변환된 수식을 스택을 이용하여 순차적으로 계산합니다.
숫자인 피연산자를 만나면 스택에 넣고 연산자를 만나면 스택에서 필요한 피연산자를 꺼내서 연산을 수행하고 최종적으로 스택에 하나의 결과만 남게 되면 그 결과를 반환해줍니다.
fun calculate(postfix:String):Double?{
var stack = Stack<Double>()
// 후위연산자를 공백으로 분리해 각 토큰을 반복
for(token in postfix.split(" ")){
// 토큰이 숫자라면 해당 숫자를 실수로 변경해서 stack에 넣기
if(token.isNumeric()){
stack.push(token.toDouble())
}
// 토큰이 연산자라면 Map에 해당 연산자가 있는지 확인하고
else if(token in operations.keys){
// 스택이 2보다 작으면 비교할 숫자가 없다는 뜻으로 null 반환
if(stack.size < 2)
return null
// 비교할 숫자가 있다면 두 숫자를 빼서 해당 연산자 수행 후 결과 스택에 넣기
var num2 = stack.pop()
var num1 = stack.pop()
var operation = operations[token] ?: return null
var result = operation.perform(num1, num2)
stack.push(result)
} else {
return null
}
}
// 스택에 남은 결과 확인 후 스택의 크기가 1이면 반환, 아니면 null 반환
return if (stack.size == 1) stack.pop() else null
}
이렇게 calculate 함수는 입력된 후위 표기식을 스택을 이용하여 순차적으로 계산하고, 최종 결과를 반환합니다. 연산자와 숫자들이 올바르게 쌍을 이루고 있어야 정확한 계산이 가능합니다 !
다른 언어들을 공부할 때 어렵다고 느꼈던 스택을 여기서 쓰게 되니, 다시 한 번 더 공부하면서 정리할 수 있는 시간을 가질 수 있어서 뭔가 신기하면서도 이를 활용해서 코드 작성을 했다는 점이 되게 뿌듯했습니다 !