계산기 프로그래밍 추가 구현

kkomin·2023년 7월 21일

Kotlin

목록 보기
5/7

텍스트## 1. 추가 구현 목표
기존의 구현했던 프로그램은 첫 번째 값, 두 번째 값, 연산자를 입력하면 연산 후 연산 결과를 출력하는 기능이었지만, 세 개 이상의 값을 넣어도 연산 후 결과 값을 출력하도록 추가로 구현할 것이고 또 하나로는 LEVEL 3를 구현하는 것이다.

  • LEVEL 3 구현
  • 세 개 이상의 값 연산 기능

2. LEVEL 3

AddOperation(더하기), SubtractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스들을 AbstractOperation라는 클래스명으로 만들어 사용하여 추상화하고 Calculator 클래스의 내부 코드를 변경하기

(1) 추상클래스 구현

//추상화된 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 클래스를 생성하고 이를 이용해서 더하기, 빼기, 곱하기, 나누기 연산기능을 각 오버라이딩 해줍니다.

(2) Calculator 클래스 코드 변경

+, -, *, / 의 짝을 통해서 연산이 이루어져야 하므로 짝 형태인 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")
      }

3. 세 개 이상의 값 연산

세 개의 값을 연산할 때, 예를 들어 10 + 3 * 2 - 7일 경우에 먼저 계산해야 하는 값은 괄호를 사용해야 하지만 후위 표기법은 괄호를 사용하지 않고도 연산이 가능합니다.

(1) 후위 표기법 (AB+)❓

후위 표기법은 연산자를 피연산자 뒤에 표기하는 방법으로 스택을 사용할 때 가장 빈번하게 등장하는 것들 중 하나입니다.

기본 연산 방법은 중위 표기법인 A+B 입니다. 그렇기 때문에 중위 표기에서 후위 표기로 변환하는 방법에 대해서 간단히 정리해보겠습니다.

(알고리즘 공부할 때 배웠던걸 여기서 쓰게 되니 되게 신기한..)

중위 표기법 ➡️ 후위 표기법
[ A*B - C/D 변환 ]
1. 각 연산자에 대해 우선순위에 따라 괄호를 사용해 표현해준다.
((AB) - (C/D))
2. 각 연산자를 대응하는 오른쪽 괄호의 뒤로 이동
((AB)
(CD)/)-
3. 괄호를 제거한다.
AB*CD/-
전위 표기법은 왼쪽 괄호의 앞으로 이동시키면 된다 !

스택과 같이 쓰이기 때문에 스택까지 수식을 연산하면 어떻게 되는지 한 번 정리해보자.

코틀린에서도 스택은 push와 pop을 이용해주면 된다.

(2) 연산자 우선순위

  1. *, /, ^, %
  2. +, -
  3. (

(3) 중위표기법 ➡️ 후위표기법 (코드)

  1. 피연산자는 스택에 넣지않고 그대로 출력
  2. 연산자
    • 스택이 비어있을경우 push
    • 스택이 비어있지 않을경우 스택의 연산자와 우선순위를 비교,
      stack top 연산자가 현재 연산자의 우선순위보다 낮을때 까지 pop 후 출력
      현재연산자 stack push
  3. 왼쪽 괄호 ( 를 만나면 stack push
  4. 오른쪽 괄호 ) 를 만나면 왼쪽 괄호 (가 나올때까지 모든 연산자 pop 후 출력 -> ( 삭제
  5. 표기식에 문자가 남지 않았다면 stack 을 비운다 pop 후 출력

스택 기능💫

  1. Stack 선언 : Stack()로 선언
  2. Stack 값 삽입 : push() 이용
  3. Stack 마지막 값 삭제 : pop() 이용
  4. Stack 마지막 값 확인 및 값 반환 : peek() 이용 값이 비어있을 때 사용시 에러 발생
  5. Stack 크기 확인 : size() 이용
  6. Stack 비었는지 확인 : isEmpty(), isNotEmpty() 이용

후위표기법으로 코드를 재설정하게 되면 main 기능도 변경해줘야 해서 추상화 클래스 부분을 뺀 나머지는 갈아엎어야...합니다.... 그래서 그냥 새로 AdditionalCal이라는 파일을 만들어서 구현하도록 하겠습니다. 추상 클래스로 구현하는 부분은 동일합니다.

(4) convert 함수 생성

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 함수는 입력받은 수식(중위 표기법)을 후위 표기법으로 변환하기 위해 사용하고, 입력된 중위 표기법 수식을 숫자 및 소수점과 연산자를 기준으로 토큰으로 분리하고, 스택을 사용해서 후위 표기법으로 변환하는 과정을 가집니다.

(5) isNumeric 함수 생성

   fun String.isNumeric() : Boolean {
     return matches("^-?\\d+(\\.\\d+)?\$".toRegex())
 }

isNumeric 함수는 정규표현식을 사용하여 주어진 문자열이 숫자인지를 판단해줍니다. toRegex는 String을 정규표현식으로 확장하는 함수입니다! 정규표현식을 사용하는 이유는 정규표현식 객체를 사용했을 때 문자열을 패턴과 비교하거나 원하는 패턴을 검색할 수 있기 때문입니다.

  • ? : 음수 기호 - 가 있거나 없는 경우 (옵션)
    • \d+ : 숫자 하나 이상이 있는 경우
    • (.\d+)? : 소수점과 숫자 하나 이상 있을 경우 (옵션)

(6) calculate 함수 생성

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 함수는 입력된 후위 표기식을 스택을 이용하여 순차적으로 계산하고, 최종 결과를 반환합니다. 연산자와 숫자들이 올바르게 쌍을 이루고 있어야 정확한 계산이 가능합니다 !

다른 언어들을 공부할 때 어렵다고 느꼈던 스택을 여기서 쓰게 되니, 다시 한 번 더 공부하면서 정리할 수 있는 시간을 가질 수 있어서 뭔가 신기하면서도 이를 활용해서 코드 작성을 했다는 점이 되게 뿌듯했습니다 !

profile
소소한 코딩 일기

0개의 댓글