티스토리 뷰

코드카타

 

최댓값과 최솟값

 

문제

 

오늘은 좀 쉬어가는 느낌인가보다.

 

 

풀이

class Solution {
    fun solution(s: String): String {
    
        // 문자열을 공백을 기준으로 나눈 뒤 그 문자열 요소를 정수로 변환한 리스트 생성
        val s = s.split(" ").map { it.toInt() }
        
        // 결과 리스트에 s의 최소값과 최댓값을 요소로 넣고
        val res = listOf(s.minOrNull(), s.maxOrNull())
        
        return res.joinToString(" ") // 공백을 포함한 문자열로 반환
    }
}

문자열을 리스트로 변환하여 다루는 문제이다.

 

 

split의 반환 자료형

fun main() {
   val s = "1 2 3"
   print(s.split(" ")) // [1, 2, 3]
}

해당 구문에서 split(" ")은 문자열을 공백을 기준으로 나누며 

인자값을 기준으로 나눈 문자열을 요소로 가지는 문자열 배열을 반환한다.

 


 

계산기 구현하기

 

Lv1

 

구현 사항

사칙연산을 수행할 수 있는 Calculator 클래스를 만들고,

클래스를 이용하여 연산을 진행하고 출력하기

 

 

클래스 구현

class Calculator {
    fun plus(a: Double, b: Double) {
        println(a + b)
    }

    fun minus(a: Double, b: Double) {
        println(a - b)
    }

    fun divide(a: Double, b: Double) {
        if (b == 0.0) println("Infinity") else println(a/b)
    }

    fun multiply(a: Double, b: Double) {
        println(a * b)
    }
}

클래스 내에 각각의 사칙연산을 수행하는 메소드 4개를 작성해준다.

소수점을 계산하는 경우도 고려하여 각 메소드는 인자값을 Double형으로 받도록 했다.

 

나누기 연산을 진행하는 메소드의 경우 제수의 값이 0일 때를 고려해야한다.

 

 

 

구현 & 실행 결과

fun main() {
    val calculator = Calculator()

    calculator.plus(20.0, 10.0) // 30.0
    calculator.minus(20.0, 10.0) // 10.0
    calculator.divide(20.0, 10.0) // 2.0
    calculator.multiply(20.0, 10.0) // 200.0
}

구현한 클래스의 인스턴스를 호출한 다음,

해당 인스턴스의 메서드를 호출하여 연산 결과를 출력한다.

 

 

 

Lv2

 

구현 사항

해당 클래스에 나머지 연산자(%)를 추가한다.

 

콘솔에서 (1번 +, 2번 -, 3번 *, 4번 /, 5번 %)을 입력하면

해당 연산을 진행하고 -1을 입력 시에는 계산기를 종료한다.

 

 

printRes

fun printRes(res: Double){
    return println("계산 결과: ${if (res % 1 == 0.0) res.toInt() else res}")
}

연산결과의 소수점 이하 값이 존재하지 않을 땐 정수로 결과값을 출력하는 함수이다. 

 

 

클래스 구현

class Calculator {
    fun plus(a: Double, b: Double) {
        printRes(a + b)
    }

    fun minus(a: Double, b: Double) {
        printRes(a - b)
    }

    fun divide(a: Double, b: Double) {
        if (b == 0.0) println("계산 결과: Infinity")
        else printRes(a / b)
    }

    fun multiply(a: Double, b: Double) {
        printRes(a * b)
    }

    fun remainder(a: Double, b: Double) {
        printRes(a % b)
    }
}

나머지 연산을 수행하는 메소드를 클래스 내에 추가해준다.

 

 

 입력값에 따른 연산 진행

fun main() {
    val calculator = Calculator()

    while (true) {
        // 연산 번호 입력 받기
        println("연산 종류: 1(더하기), 2(빼기), 3(곱하기), 4(나누기), 5(나머지), -1(종료)")
        println()

        // 입력이 올바르지 않을 경우 처리
        var operation = 0
        while (operation !in 1..5 && operation != -1){
            print("연산 번호 입력: ")
            operation = readln().toInt()

            if (operation !in 1..5 && operation != -1) {
                println("잘못된 입력입니다. 다시 시도하세요.")
                println()
            }
        }

        // 계산기 종료 로직
        if (operation == -1) break

        // 연산 진행할 두 수 입력 받기
        print("첫 번째 숫자를 입력: ")
        val a = readln().toDouble()
        print("두 번째 숫자를 입력: ")
        val b = readln().toDouble()
        println()

        // 연산 번호에 따른 연산 결과 출력
        when (operation) {
            1 -> calculator.plus(a, b)
            2 -> calculator.minus(a, b)
            3 -> calculator.multiply(a, b)
            4 -> calculator.divide(a, b)
            5 -> calculator.remainder(a, b)
        }

        // 다음 계산 과정을 위해 개행
        println()
        println("-".repeat(30))
        println()
    }
}

while 반복문을 통해 Lv2에서 요구하는 기능을 구현한다.

 

입력한 연산 번호가 1 ~ 5에 포함되어있지 않거나 -1이 아니라면

안내메시지와 함께 다시 연산번호를 입력하도록 한다.

 

연산 번호의 값을 -1로 주게 되면 해당 반복문이 break되면서 계산기가 종료된다.

 

when함수를 통해 연산 번호에 따라 a와 b의 연산 결과를 출력한다.

 

 

실행 결과

 

 

Lv3

 

구현 사항

AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기)

연산 클래스를을 만든 후 클래스간의 관계를 고려하여 Calculator 클래스와 관계를 맺기

 

 

각 연산 클래스 구현

class AddOperation {
    fun operate(a: Double, b: Double) {
    	printRes(a + b)
    }
}

class SubtractOperation {
    fun operate(a: Double, b: Double) {
    	printRes(a - b)
    }
}

class MultiplyOperation {
    fun operate(a: Double, b: Double) {
    	printRes(a * b)
    }
}

class DivideOperation {
    fun operate(a: Double, b: Double) {
        if (b == 0.0) println("계산 결과: Infinity")
        else printRes(a / b)
    }
}

각 연산 클래스들의 operate 메서드는 연산결과를 출력하는 함수를 반환한다.

 

 

계산기 클래스 구현

class Calculator {
    private val addOperation = AddOperation()
    private val subtractOperation = SubtractOperation()
    private val multiplyOperation = MultiplyOperation()
    private val divideOperation = DivideOperation()

    fun add(a: Double, b: Double) {
        addOperation.operate(a, b)
    }

    fun subtract(a: Double, b: Double) {
        subtractOperation.operate(a, b)
    }

    fun multiply(a: Double, b: Double) {
        multiplyOperation.operate(a, b)
    }

    fun divide(a: Double, b: Double) {
        divideOperation.operate(a, b)
    }
}

사칙연산 클래스들의 인스턴스를 private로 선언하여 

클래스 내부에서만 해당 인스턴스들을 사용할 수 있도록 한다.

 

사칙 연산 결과를 출력하는 함수에서

각 사칙연산 클래스의 operate함수를 호출한다.

 

 

Lv2와의 클래스 구조 비교

Lv2에선 모든 연산을 단일 클래스 내에서 처리했지만

Lv3의 구조에선 각 연산이 별도의 클래스로 분리되었다.

 

 

Lv2와 비교하여 개선된 점

 해당 클래스는 각 연산에 대한 책임이 해당 연산 클래스에만

존재하여 결과적으로 단일 책임 원칙을 준수한다.

 

 계산기 클래스는 각각의 연산 클래스를 사용하여 결과를 얻기에

각 연산의 내부 구현에 대해 알 필요가 없어 클래스 간의 의존성이 감소한다.

 

해당 클래스는 각 연산이 독립적인 클래스로 적용되어

다른 파일에서 해당 클래스의 재사용이 가능하다.

 

 

 

Lv4

 

구현 사항

AbstractOperation라는 클래스에서 사칙연산 클래스들을

추상화하고 Calculator 클래스의 내부 코드를 변경한다.

 

 

추상 메소드 작성

interface Operation {
    fun operate(a: Double, b: Double)
}

인터페이스 내에 operate라는 추상 메소드를 정의한다.

 

 

확장 함수 정의

fun Operation.printRes(res: Double) {
    println("계산 결과: ${if (res % 1 == 0.0) res.toInt() else res}")
}

클래스 내에 위치시키기 애매한 함수는 인터페이스의 확장 함수로 정의한다.

 

 

추상화 클래스 활용

class AddOperation : Operation {
    override fun operate(a: Double, b: Double) {
        printRes(a + b)
    }
}

class SubtractOperation : Operation {
    override fun operate(a: Double, b: Double) {
        printRes(a - b)
    }
}

class MultiplyOperation : Operation {
    override fun operate(a: Double, b: Double) {
        printRes(a * b)
    }
}

class DivideOperation : Operation {
    override fun operate(a: Double, b: Double) {
        if (b == 0.0) println("계산 결과: Infinity")
        else printRes(a / b)
    }
}

각 연산 클래스에서 인터페이스를 상속하고 

추상화 클래스의 연산 메소드를 오버라이딩 한다.

 

 

계산기 클래스 구현

class Calculator {
    fun calculate(operation: Operation, a: Double, b: Double) {
        operation.operate(a, b)
    }
}

해당 calculate 메서드는 Operation 인터페이스를 인자값으로 받아,

그에 따라서 연산을 수행하는 역할을 한다.

 

 

함수 호출

when (operation) {
    1 -> calculator.calculate(AddOperation(), a, b)
    2 -> calculator.calculate(SubtractOperation(), a, b)
    3 -> calculator.calculate(MultiplyOperation(), a, b)
    4 -> calculator.calculate(DivideOperation(), a, b)
}

operation 위치에는 인터페이스를 상속하는 각 사칙연산 클래스들이 들어간다.

 

 

Lv3 클래스 구조와의 차이

Lv3에서는 계산기 클래스가 각각의 사칙연산 클래스에 의존하였는데,

현재 클래스 구조에서는 추상화된 Operation 클래스에 의존한다.

 

이는 의존성 역전 원칙을 준수하며, 코드의 유연성과 확장성을 높인다.

 

 

의존성 역전 원칙

의존성 역전 원칙이란 의존 관계를 맺을 때

변하기 쉬운 것보다는 변하기 어려운 것에 의존해야 한다는 원칙이다.

(구체화된 클래스에 의존하기보다는 추상 클래스나 인터페이스에 의존해야한다)

 

 

Lv3보다 개선된 점

해당 클래스 구조에서 새로운 연산을 추가하려면 단순히

Operation 클래스를 상속하고 operate() 메서드를 구현하기만 하면 된다.

 

Lv3와 달리 Lv4에서 계산기 클래스는 추상화된 인터페이스를 사용하여

새로운 연산이 추가되어도 수정할 필요가 없기에 유연한 확장성을 가진다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함