스파르타 코딩클럽

[스파르타 코딩클럽] 후위연산식과 계산기

딸기토끼0623 2024. 4. 25. 09:43

최종 코드: https://github.com/spartaCoding-2-4/ch2.SoonYong

0. 과제 정의 (두 수 연산 abstract_class Branch)

1. 더하기, 빼기, 나누기, 곱하기 연산을 수행할 수 있는 Calculator 클래스를 만들것.

2. 아래 연산 클래스들을 AbstractOperation라는 클래스명으로 만들어 사용하여 추상 클래스로 정의하고 Calculator 클래스의 내부 코드를 변경합니다.

  • AddOperation(더하기)
  • SubtractOperation(빼기)
  • MultiplyOperation(곱하기)
  • DivideOperation(나누기)

<결과 화면>
<간략 구조>

간단한 두 수 연산으로 구현해서 달리 코드 설명이 필요없을 정도로 아주 간단한 구조로 이루어져있다.

자세한 코드는 abstract_class 브랜치로 가면된다.


1. 수식 입력, 문자열 파싱과 후위연산 (postFix_v1.0.0)

하지만 여기서 끝내면 재미가 없겠죠?!

그래서 3가지를 도입하기로 했다.

 

a. 연산자 Enum 클래스 강화 (중요!!)

b. 수식을 문자열로 직접 입력받아 parsing 할것.

c. 중위연산식을 후위연산식으로 바꾸기.

 

어차피 컴퓨터가 계산을 쉽게 하기 위해선 후위연산식으로 바꿔야만 한다. 기존의 파일과 두수 연산에 대한 기초는 그대로 두고, 문자열 파싱 함수 하나, 후위식 변환 파일 하나 총 2개를 추가하기로 했다.

 

거기에 더해 기존 OperatorsEnum 클래스에 Char형 심볼과 연산자간 우선순위를 추가하기로 했다.

<바뀐 결과 화면>


a. OperatorsEnum.kt (중요!!)

enum class OperatorsEnum(val symbol: Char, val priority: Int) {
    ADD('+', 1),
    SUB('-', 1),
    MULTI('*', 0),
    DIV('/', 0),
    OPEN_PAREN('(', 2),
    CLOSE_PAREN(')', 2),
    UNKNOWN( ' ', 3)
}

symbol: 사용자가 입력한 문자형식의 연산자와 해당 Enum을 맵핑할때 필요하다.

priority: 중위식을 후위식으로 바꿀때 연산자간 우선순위가 필요하다.

 

위 두 속성을 Enum 클래스에 추가하면서 수많은 if문들과 while문을 없앨 수 있었다.


b. stringToInfix.kt

수식을 문자열로 직접 입력받아 parsing 하는 함수

아래 코드블럭 최상단에 있는 input을 여러분이 원하는 수식으로 바꾸어서 입력해보자!

 

c. infixToPostfix.kt

b 에서 파싱된 수식을 infixToPostifx에 삽입하면 후위식으로 바뀌어서 표현되게 된다!!

아래 코드블럭 최상단에 있는 infix를 여러분이 원하는 수식으로 바꾸어서 입력해보자!


3. Any Class의 도입 (postFix_v1.0.1)

하지만 여기서 끝내면 재미가 없겠죠? x2

지금 위 코드에서는 모든 postfix, infix MutableList들이 String 타입만을 담도록 되어있다. "코딩하는 내내 Java Object처럼 아무거나 담게 할 수 있으면 좋을텐데.." 라는 생각을 했다. Float, OperatorsEnum등 아무거나 담을 수 있다면 더 코드가 간결해지고 직관적이게 될 것이라고 생각했다.

 

그래서 바꿨다!! 야무지게 v1.0.1 로 버전업도 해놓았다!

 

var postfix = mutableListOf<String>() 에서

var postfix = mutableListOf<Any>() 으로!

 

자세한 내용은 아래에 적어놨다. ㅎㅎhttps://strawberryrabbit.tistory.com/4

 

[Kotlin] Any 타입과 MutableList

1. MutableListAny> 아무거나 넣을 수 있는 가변리스트The root of the Kotlin class hierarchy. Every Kotlin class has Any as a superclass.코틀린 클래스의 상속 뿌리입니다. 모든 코틀린 클래스는 Any 클래스를 슈퍼클래

strawberryrabbit.tistory.com


4. Singleton 도입 (postFix_v1.0.2)

하지만 여기서 끝내면 재미가 없겠죠? x 3

아직 Singleton이 남았다. 생각해보니 여기에 등장하는 Calculator 부터 Operation까지 싱글톤 class여도 된다는 생각이 들었다. 오히려 확장성을 생각하면 안정적일 것이고 말이다.

 

그래서 아래와 같이 바꿨다! 야무지게 v1.0.2 로 버전업도 해놓았다!

Class 인스턴스 생성자 호출시 받던 인자를 함수쪽으로 내리고, Class 전체를 object로 선언해버렸다.

package main.operations

object MultiplyOperation: OperationInterface {
    override fun operate(a: Float, b: Float): Float {
//        print("$a * $b = ")
        return a * b
    }
}

5. fun Interface 도입 (postFix_v1.0.3)

하지만 여기서 끝내면 재미가 없겠죠? x 4

팀원에게 Kotlin 에서는 fun interface라는 것이 있다는 말을 들었다. 어차피 Calculator 클래스나 Operations 클래스나 fun이 하나밖에 없는상황에서, class 나 obj 를 선언하지 않아도 되지 않나? 라는 생각이 들었다.

 

Operations 패키지 안의 코드를 아래와 같이 바꾸었다!

 

 

[참고] 후위식이란? 중위식을 후위식으로 바꿀때의 규칙은?

  1. 숫자는 그대로 출력한다.
  2. 만약 스택이 비어있다면 연산자를 그냥 스택에 넣는다.
  3. (스택의 top에 있는 연산자의 우선순위 < 현재 연산자의 우선순위) 이면 현재 연산자를 그냥 스택에 넣는다.
  4. (스택의 top에 있는 연산자의 우선순위 >= 현재 연산자의 우선순위) 이면 2번 혹은 3번 상황이 될 때까지 pop 하여 출력하고 연산자를 스택에 넣는다.
  5. 우선순위는 (괄호 연산자) < (더하기 빼기) < (곱하기 나누기)이다.
  6. 여는 괄호는 스택에 그냥 추가한다.
  7. 닫는 괄호는 여는 괄호가 나올 때까지 스택을 pop 하여 출력한다. 다 출력하고 난 뒤 괄호들은 버린다.
  8. 모든 수식을 다 사용했다면 스택이 빌 때까지 pop하여 출력한다.

[번외] 예외처리

divide by 0 같은 예외처리는 안했다. ㅎ 왜냐고? 놀랍게도 계산이 되거덩..

<이게 되네..>

극한의 개념을 생각한다면 가능한 연산이긴 하다.