티스토리 뷰

코드카타

 

JadenCase 문자열 만들기

 

문제

 

 

풀이

class Solution {
    fun solution(s: String): String {
        return s.split(" ").joinToString(" ") { it.toLowerCase().capitalize() }
    }
}

문자열을 람다문을 실행시킬 수 있는 형태로 바꿔주고

각 요소를 전부 소문자로 변경한 뒤 첫글자만 대문자로 바꿔준다.

 

 

문자열에서 람다문 사용을 가능하게 하기

s.split(" ").joinToString(" ")은 s와 반환결과는 같지만

전자의 구문에서는 joinToString 함수에 람다식을 전달하여

각 단어에 대해 변환 작업을 수행할 수가 있게 된다.

 

 

capitalize()

fun main() {
   val s = "hello world"
   print(s.capitalize()) // Hello world
}

문자열에서 첫 글자를 대문자로 변환하고 나머지 글자를 소문자로 변환하는 함수이다.

 


 

계산기 클래스 구현하기

 

Lv5

 

구현 사항

수식을 입력받고 연산자의 우선순위를 고려하여 연산 결과를 출력한다.

(괄호나 사칙연산 기호의 연산 우선 순위를 고려해야 한다.)

 

 

중위 표기법과 후위 표기법

val infix = "1 + 2 + 3" // 중위 표기법
val postfix = "1 2 + 3 +" // 후위 표기법

연산자가 중간에 위치하는 것은 중위 표기법,

연산자가 뒤에 위치하는 것은 후위 표기법이다.

 

중위 표기법 사람이 사용하기 쉽게 식을 표현하는 방법이라면

후위 표기법 컴퓨터가 연산하기 쉽게 식을 표현하는 방법이다.

 

 

연산자의 우선순위 지정

private fun opPriority(op: Char): Int {
    return when (op) {
        '+', '-' -> 1
        '*', '/' -> 2
        else -> 0 // 괄호는 0을 반환한다.
    }
}

해당 함수는 연산자 기호를 인자값으로 받은 뒤 그 값에 따라 정수를 반환한다.

연산자의 우선순위를 "괄호 연산자 < 덧셈,뻴셈 연산자 < 곱셈,나눗셈 연산자"로 준다.

 

 

중위 표기법을 후위 표기법으로 반환

private fun infixToPostfix(str: String): String {
    val num = StringBuilder() // 출력 문자열(가변형)
    val stack = mutableListOf<Char>() // 연산자 순서를 저장할 스택
    
    // 주어진 문자열(연산식)을 순회하면서 각 문자를 확인한다
    for (i in str) {
        when {
            // 현재 문자가 숫자나 소수점이면, 바로 출력 문자열에 추가
            i.isDigit() || i == '.' -> num.append("$i") 
            
            // 현재 문자가 여는 괄호라면, 스택에 추가
            i == '(' -> stack.add(i)
            
            // 현재 문자가 닫는 괄호라면, 여는 괄호가 나올 때까지
            // 스택에서 연산자를 출력 문자열에 추가한다.
            i == ')' -> {
                while (stack.isNotEmpty() && stack.last() != '(') {
                    num.append(" ${stack.removeAt(stack.lastIndex)}")
                }
                stack.removeAt(stack.lastIndex)
            }
            
            // 만약 현재 문자가 연산자인 경우, 스택에 있는 연산자들의 우선순위를 확인하여 
            // 현재 연산자보다 우선순위가 높거나 같은 연산자들을 
            // 출력 문자열에 추가한 후, 현재 연산자를 스택에 추가한다.
            i in "+-*/" -> {
                while (stack.isNotEmpty() && opPriority(i) <= opPriority(stack.last())) {
                    num.append(" ${stack.removeAt(stack.lastIndex)}")
                }
                stack.add(i)
                num.append(" ")
            }
        }
    }
	
    // 마지막으로 스택에 남아있는 모든 연산자를 출력 문자열에 추가한다.
    while (stack.isNotEmpty()) {
        num.append(" ${stack.removeAt(stack.lastIndex)}")
    }

    return num.toString().trim()
}

연산자의 우선순위를 고려한 연산을 진행하려면 

연산식의 표기법을 중위 표기법에서 후위 표기법으로 바꿔줘야 한다. 

 

 

이해를 돕기 위한 표기법 변환 예시

print(infixToPostfix("(1 + 2) * 3")) // 출력: 1 2 + 3 *

stack: ['('] -> ['(', '+'] -> ['('] -> ['(', '*'] -> ['(']
num: "1" -> "1 " -> "1 2" -> "1 2 +" -> "1 2 +" -> "1 2 + 3" -> "1 2 + 3 *
(공백은 기본적으로 무시한다.)

'(': 여는 괄호이므로 스택에 추가한다.

'1': 숫자이므로 num에 추가

'+': 스택이 비어있지 않고 여는 괄호보다 연산자 우선순위가 높으므로 스택에 추가하고,
     num에는 공백을 추가한다.

'2': 숫자이므로 num에 추가

')': 스택이 비어있지 않고 스택의 마지막 요소가 여는 괄호가 아니므로
     num에는 현재 스택의 마지막 요소('+')를 추가함과 동시에 스택에서는 그 값을 지운다
     
'*': 현재 스택에는 여는 괄호밖에 없으므로 스택에 추가

'3': 숫자이므로 num에 추가, 현재 연산식의 모든 요소를 순회하였으므로
     공백과 함께 스택에 남아있는 연산자 '*'을 num에 추가한다.
     해당 로직은 스택이 비어있다면 종료되므로 최종적으로 스택에는 '('만이 남게된다.

함수 어떻게 실행되는지 이해하는데만 1시간 넘게 걸린 거 같다 ㅋㅋ...

 

 

 

후위 표기법 연산식 계산하는 함수

private fun calculatePostfix(postfixEx: String): Double {
    val stack = mutableListOf<Double>()

    for (i in postfixEx.split(" ")) {
        // 피연산자인 경우 스택에 추가
        if (i.matches(Regex("-?\\d+(\\.\\d+)?"))) stack.add(i.toDouble())

        // 연산자인 경우 스택에서 피연산자들을 꺼내서 계산하고 결과를 다시 스택에 추가
        else {
            val op1 = stack.removeAt(stack.lastIndex)
            val op2 = stack.removeAt(stack.lastIndex)
            when (i) {
                "+" -> stack.add(op2 + op1)
                "-" -> stack.add(op2 - op1)
                "*" -> stack.add(op2 * op1)
                "/" -> stack.add(op2 / op1)
            }
        }
    }

    return stack.first()
}

해당 함수는 후위 표기법으로 된 수식을 계산한 후 Double형 값을 반환한다.

공백을 기준으로 수식을 분리하여 요소에 따른 연산을 진행한다.

 

후위 표기법 연산식은 연산자 앞에 최소한 2개의 피연산자가 존재한다.

 

수식을 차례대로 순회하며 i가 피연산자라면 스택(리스트)에 추가하고, 

i가 연산자라면 스택에서 최근에 추가된 피연산자 2개를 꺼내서

해당 연산을 수행하고 연산 결과를 스택에 넣는다.

 

연산식(문자열 리스트)의 순회를 마친 후 스택의 마지막 값은

후위 표기법 연산식의 계산결과에 해당한다. 

 

 

연산 결과 반환 함수

fun calculate(ex: String): Any {
    val postfix = infixToPostfix(ex)
    val res = calculatePostfix(postfix)
    return if (res % 1.0 == 0.0) res.toInt() else res
}

연산식(문자열)을 인자값으로 받아 연산 결과를 반환하는 함수이다.

다른 파일에서 쓸 수 해당 메서드를 갖다 쓸 수 있도록 public으로 선언한다.

 

기본적으로는 실수를 반환하고 연산결과의 소수점 이하값이 0일 경우

연산결과를 정수로 변환하여 반환한다.

 

 

테스트 ㄱㄱㄱ

fun main() {
    val calculator = Calculator()

    print("수식을 입력하세요: ")
    val input = readln()

    val result = calculator.calculate(input)
    println("계산 결과: $result")
}

calculate함수가 의도한 대로 잘 동작하는지 테스트를 진행해준다.

내일은 계산기 UI를 만들고 해당 함수를 갖다 써서 계산 기능을 구현할 것이다. 

 

 

실행 결과

 

 

곱연산이 우선적으로 진행이 된 모습이다.

 

 

 

괄호 연산이 우선적으로 진행이 된 모습이다.

 

 

 

실수형 값도 문제없이 잘 반환하는 모습이다.

 


 

학습 소감

 

 

필수 과제도 아니고 너무 어려워서 그냥 안하려다가

문득 계산기 하나 제데로 못만들면서 앱 개발자를 하려는 게

말이 안되는 거 같다는 생각이 들어서 오기로 구현해냈다.

 

(주말인데 이거 왜 하고 있어야 되나.. 하고 내적갈등 씨게 하다가

계속 어딘가 찝찝함이 남아서 오늘 그냥 해버림) 

 

 

  

소마고 1학년 때 후위 표기법을 배운적이 있는데

이제서야 그걸 왜 배우는지 깨닫게 된 거 같다.

 

사실 까먹어서 연산식 표기법 개념 다시 공부했다.

 

나중에 커리큘럼 쭉 따라가다보면 소마고 cs시간에 배운

개념을 활용하게 되지 않을까 싶다.

 

 

 

저걸 사실 내 머리로 생각한 건 아니고 지피티를 열심히 괴롭혀서 방법을 알아냈다.

이제 기능 구현하려고 2시간, 로직 이해하려고 1시간 정도해서 총 3시간 정도 질문했다.

 

중위 표기법으로 작성된 연산식을 후위 표기법으로 변환하는 로직을 이해하는 게 젤 어려웠다.

 

 

 

기능은 간단하지만 그것을 구현하는 건 간단하지 않다는 

개발의 법칙을 오늘도 여실히 체감하고 가는 시간이었다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함