Kotlin) 계산기 만들기

성승모·2024년 6월 5일
0

2024.06.05

조건) AbstractOperation라는 추상 클래스를 생성하여 AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스 정의

계산기의 수행 절차를 생각해보자.

  • 절차 1. 수식을 입력받는다.
  • 절차 2. 수식을 분석한다.
  • 절차 3. 계산한다.
  • 절차 4. 반환한다.

-> 매우 간단해보였다...


조건은 간단하게 수행할 수 있었다.

interface AbstractOperation {
    fun doOperation(a: Float, b: Float): Float
}

class AddOperation: AbstractOperation {
    override fun doOperation(a: Float, b: Float): Float = a + b
}
class SubtractOperation: AbstractOperation {
    override fun doOperation(a: Float, b: Float): Float = a - b
}
class MultiplyOperation: AbstractOperation {
    override fun doOperation(a: Float, b: Float): Float = a * b
}
class DivideOperation: AbstractOperation {
    override fun doOperation(a: Float, b: Float): Float = a/b + a%b
}
  • 입력을 받아보자. replace를 이용하여 input의 공백을 제거한다.
    예상 값은 공백과 '='을 뺀 수식이다.
  • 계산을 담당하는 클래스인 Calculator를 선언한다.
  • Calculator 내부에서 위의 연산 수행 클래스들을 선언한다.
fun main() {
    val calculator = Calculator()
    val br = BufferedReader(InputStreamReader(System.`in`))
    val input = br.readLine().replace(" ", "")

    val result = calculator.doIt(input)
    println(result)
}

Calculator.doCalculate(str: String)을 정의하였으며 숫자만 모아놓은 numbers와 연산자들만 모아놓은 operators를 생성하였다. 반복문과 when을 이용하여 앞에서부터 +, -, *, %을 수행하게 하였다. 하지만 계산 시 우린 *와 %를 우선 수행한다.


문제1.*와 % 우선 수행

  • operators 중 *와 %를 찾아 연산에 해당되는 numbers[index] 와 numbers[index+1]를 가져와 연산을 수행하였다.
  • 수행결과를 numbers[index]에 넣고 numbers[index+1]엔 -1을 넣어주었다.
  • numbers를 반복문에서 이용하다가 -1을 만나면 continue하면 되겠다.
private fun doCalculate(str: String): Float {
		//정규표현식을 이용하여 input값인 str에서 숫자만 저장한다. 
        val numbers = ArrayList(str
            .replace("[^₩.0-9]".toRegex(), " ")
            .split(" ")
            .map {
                it.toFloat()
            }
        )
        //반대로 이용하여 operator만 저장한다.
        val operators = str.replace("[₩.0-9]".toRegex(), " ").split(" ").filter {
            it.isNotEmpty()
        }

        //곱셈, 나눗셈 먼저 계산
        operators.forEachIndexed { index, it ->
            if (it == "*") {
                val n = multiply.doOperation(numbers[index], numbers[index+1])
                numbers[index] = n
                numbers[index + 1] = -1F
            } else if(it == "%") {
                val n = divide.doOperation(numbers[index], numbers[index+1])
                numbers[index] = n
                numbers[index + 1] = -1F
            }
        }

		//계산 결과 값인 result에 -,+ 연산들을 하여 더한다.
        var result = numbers.first()
        for((index, ele) in numbers.drop(1).withIndex()) {
            if(ele == -1F) continue

            result = when(operators[index]) {
                "+" -> add.doOperation(result, ele)
                "-" -> subtract.doOperation(result, ele)
                else -> throw Exception("* and % is not filtered.")
            }
        }

        return result
    }

잘 작동한다.


문제2. 괄호 우선 수행

큰 문제였다... 한 4-5시간 걸린듯하다.

  • 우선 괄호를 찾는다.
    • 괄호가 몇개 있을줄 알어..?
    • 중첩되어있으면..?
  • 괄호를 우선 수행...
    • 괄호 안에 괄호가 또 있으면..?
    • 괄호 안을 수행하고 또 다시 이용해야 되잖아...

-> 재귀 함수를 이용하기로 하였다.

우선 재귀 함수를 이용하기 위해 '기본 수행 단위'와 '탈출 조건'을 정의해보자

  • 기본 수행 단위: 괄호가 없는 수식의 연산 수행
  • 탈출 조건: 괄호가 더이상 존재 X

우선 기본 수행 단위는 위 doCalculate를 이용하면 되겠다.
탈출 조건은 if에서 ()의 유무를 확인하면 쉽다.

그러면 함수를 정의해보자.

private fun containsBracket(str: String): Boolean = str.contains("(")
private fun findBrackets(str: String): List<String> {
        var numOfOpen = 0
        var numOfClose = 0
        val bracketList = mutableListOf<String>()
        var firstOpenIndex = 0

        str.forEachIndexed { i, ele ->
            if(ele == '(') {
                numOfOpen++
                if(numOfOpen == 1) firstOpenIndex = i
            }
            else if(ele == ')') {
                numOfClose++

                if(numOfOpen == numOfClose) {
                    bracketList.add(str.substring(firstOpenIndex, i+1))
                    numOfClose = 0
                    numOfOpen = 0
                }
            }
        }

        return bracketList
    }
fun doIt(str: String): Float {
        return if(!containsBracket(str)) {
            doCalculate(str)
        } else {
            val brackets = findBrackets(str)
            var result = str

            for(ele in brackets) {
                var removedBracket = ele.substring(1, ele.length-1)
                result = result.replace(
                    ele,
                    doIt(removedBracket).toString()
                )
            }
            return doIt(result)
        }
    }

if문으로 () 포함 유무를 확인하여 탈출한다.
괄호 내부 수식을 재귀 처리한다. 그리고 그 값을 "{수식}" 과 replace 한다.
잘 작동한다.

복잡하게 생각한거에 비해 코드는 짧게 나온거 같다... 어렵다.
findBrackets 함수와 doCalculate 함수들은 좀 더 간결하게 작성할 수 있을거 같다. 나중에 고쳐보자.

profile
안녕하세요!

0개의 댓글