https://school.programmers.co.kr/learn/courses/30/lessons/135808

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

class Solution {
    fun solution(k: Int, m: Int, score: IntArray): Int {
        var answer: Int = 0
        var appleList = listOf<Int>()
        var appleBox = mutableListOf<Int>()
        
        appleList = score.sortedDescending()
        for(i in 0 until appleList.size) {
            if(appleBox.size < m) {
                appleBox.add(appleList[i])
                if(appleBox.size == m) {
                    answer += appleBox.minOf{it} * m
                    appleBox.clear()
                }
            }
        }
        return answer
    }
}

 

이런식으로 풀었더니 시간이 길게 걸리는게 275ms가 있었다,,, 

이런식으로는 안되겠다 싶어 좀 더 꼼꼼히 보니 결국엔 사실 m사이즈만큼의 각 배열에서 마지막 숫자만큼만 m과 곱해준다는 규칙성을 발견하고 코드를 다시 수정했다.

class Solution {
    fun solution(k: Int, m: Int, score: IntArray): Int {
        var answer: Int = 0
        score.sortDescending()
        var lastNum = 0
        score.forEach{
            lastNum++
            if(lastNum%m == 0){
               answer+= it*m
            }
        }
        return answer
    }
}

 배열에 굳이 넣을 필요없이 m 에 딱 떨어지는 숫자일 때 마다 m에 딱 떨어지는 숫자에 m을 곱해주면 된다.

효율이 2배이상 좋아졌다,,

https://school.programmers.co.kr/learn/courses/30/lessons/159994

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

또 하나 배운 알고리즘 문제였다..

코드 먼저 보자.

class Solution {
    fun solution(cards1: Array<String>, cards2: Array<String>, goal: Array<String>): String {
        var answer: String = "Yes"
        var index1 = 0
        var index2 = 0
        
        goal.forEach {
            if(cards1.size > index1 && cards1[index1] == it){
                index1++
            }
            else if(cards2.size > index2 && cards2[index2] == it){
                index2++
            }
            else {
                return "No"
            }
        }
        return answer
    }
}

 

if 문에서 &&연산자를 사용할 때도 조건의 순서가 중요하다는 점을 배웠다.

처음에는 && 연산자 사이에 조건을 별 생각없이 지금과 반대로 했었다. 

if(cards1[index1] == it && cards1.size > index1)

else if(cards2[index2] == it && cards2.size > index2)

이렇게 하니 돌아오는 것은,,, 런타임 에러,,,

보아 하니 인덱스 값이 길이를 초과한 모양이다.

테스트 2번 예제와 else if 구절을 뜯어보면 이해가 가능하다.

["i", "water", "drink"] ["want", "to"] ["i", "want", "to", "drink", "water"] "No"
else if(cards2[index2] == it && cards2.size > index2){
      index2++
}

 

테스트 2번에서는 cards2의 index2값이 0에서 "want"를 만나 1이 되고 "to"를 만나 2가 된다.

이 후 "drink"를 찾으러 가는데 cards1에서는 못찾으니 else if로 내려오게 되면서 문제가 발생한다.

&&연산자 첫 번째 조건에서 cards2[index2] == it 여기서 cards1의 인덱스는 0과 1 뿐인데 index2의 값인 2가 들어와서 

배열인덱스의 범위를 초과한 값을 넣은 것이다. 

 

&&연산자 양옆의 조건 위치를 반대로 해주니 통과가 되었다.

이를 통해 && 연산자의 첫 번째 조건을 우선적으로 검사를 한다는 것을 알 수 있었고 결과에 큰 영향을 미칠 수 있으므로 순서도 잘 생각해서 배치해주어야 한다는 걸 배웠다.

 

 

새 프로젝트를 열고 git 연동 작업을 하고 이제 git add .을 했는데 이런 오류가 잔뜩 뜬다...

 

오류내용을 보자면 이렇다.

 

LF ( Line-Feed )  -> Mac/ Linux 에서 사용하는 줄바꿈 문자열

CRLF( Carriage Return Line-Feed ) Windows 에서 사용하는 줄바꿈 문자열

 

위에 내용처럼 OS마다 사용하는 줄바꿈 형식이 다른데 현재 내가 사용하는 OS는 Windows인데 파일들이 LF형식이 적용되어 있어서 Git이 내 OS에 맞게 CRLF로 변경해주겠다는 내용의 경고문이다.

 

똑똑한 녀석,,

 

사실 경고문 무시하고 하던대로 해도 정상작동 하는 것 같다. 하지만 혹시 모를 파일 손상이 있을 수 있으므로

아래 내용을 잘 보고 따라치자.

 

core.autocrlf 설정을 통해 해결할 수 있다. Git에 코드를 커밋할 때 LF와 CRLF를 서로 변환해주는 기능이다. 또한 시스템 전체에 적용할 것이라면 global 옵션을 추가해주고, 해당 프로젝트에만 적용한다면 제외하여 작성해주면 된다.

각각의 체제에 맞게 아래의 명령어를 선택하여 입력해준다.

 

Windows

git config core.autocrlf true
git config --global core.autocrlf true

 

Mac, Linux

git config core.autocrlf input
git config --global core.autocrlf input

 

 

기능 해제를 하고 싶다면,

git config --global core.autocrlf false

위의 방법과 달리, core.autocrlf 기능을 해제하는 방법도 있다. 해제함으로써 줄바꿈 문자열을 변환하지 않아도 에러 메시지는 안뜬다. 위의 방법과 해당 방법 중에서 선택하여 입력하면 될 것 같다.

 

 

 

'Git,Github' 카테고리의 다른 글

Git, Github 협업 프로세스  (0) 2023.12.04
Git 필수 명령어 모음  (2) 2023.11.22
Git 과 Github 이란?  (2) 2023.11.20

 

안드로이드 스튜디오를 키고 run을 돌리면 자꾸 뜨는 에러다.

 

읽어보면 컴파일SDK 버전 문제인듯 하다.

 

컴파일 SDK 버전과 defaultConfig 안에 targetSDK의 버전이 모두 33이면 오류가 뜬다.

 

이 경우 위와 같이 컴파일 SDK 버전을 34로 바꿔주고 targetSDK 줄을 삭제해버리면 오류가 사라진다.

 

 

 

이전에 만들었던 Lv4는 의존성 역전 원칙(DIP, Dependency Inversion Principle) 을 수행한 내용이지만 OOP의 원칙 중 하나인 개방-폐쇄 원칙(OCP, Open-Closed Principle)을 어긴 사례이다,,,

class Calculator {

    fun calculator(num1: Double, num2: Double, op: AbstractOperation): Double {
        return when (op) {
            is AddOperation -> {op.operation(num1,num2)}
            is SubstractOperation -> {op.operation(num1,num2)}
            is MultiflyOperation -> {op.operation(num1,num2)}
            is DivideOperation -> {op.operation(num1,num2)}
            else -> throw IllegalArgumentException("잘못된 연산자 입력값입니다.")
        }
    }
}

 

추상화 클래스를 적용했을 때 Lv3 보다는 깔끔해지기도 했고 해서 괜찮게 했다 생각했으나, 마음 한 편으로는 기능 추가할 때 한 줄씩 계속 추가해야 한다면 기능이 100개가 추가가 된다면 100줄 추가해야하는데 그게 유지 보수가 간편하다 말해도 되나? 싶었다. 물론 그 전보다는 코드량이 꽤 줄었지만 상위 모듈은 코드 수정 필요없이 작동시키고 싶어서 찾아봤다.

 

찾아보니 개방-폐쇄 원칙이라는게 있었다. 개방-폐쇄 원칙이란 '소프트웨어 개체(클래스, 모듈, 함수 등등)는 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다'는 프로그래밍 원칙이다.

 

즉, 기능이 변하거나 확장 가능하지만, 함수의 본질적인 코드(해당 기능의 코드)는 수정하면 안된다는 원칙이다.

 

하지만, 나는 기능이 확장되거나 변하면 calculator 함수의 내용을 수정해야 한다. 앞선 글에서 이야기한거와 같이

제곱연산자 기능을 넣으면 is SquareOperation -> {op.operation(num1,num2)} 이 내용도 상위모듈에 추가해야 한다.

이 자체가 개방-폐쇄 원칙을 위반한 코드다.

 

원칙에 부합하게 열심히 방법을 찾아보고 코드를 눈 뚫어져라 쳐다보니 이를 획기적으로 고치는 방법이 있었다.

class Calculator {

    fun calculator(num1: Double, num2: Double, op: AbstractOperation): Double {
        return op.operation(num1,num2)
    }
}

 

생각해보니 op를 받아올 때 이미 연산 클래스를 받아오기에 그 안에 있는 연산 메서드를 이용하면 끝이었다.

이 한줄을 통해 연산 클래스의 기능이 확장이 되거나 변경이 되어도 같은 기능을 수행하게 된다.

즉, 상위 모듈의 코드 변경없이 기능 확장이 가능해졌다.

//main 안에 op 입력 구현 부분

fun isValidOperator() : AbstractOperation {
    while (true) {
        try {
            var op = readLine()!!.toInt()
            if (op in 1..4) {
                when(op) {
                    1 -> return AddOperation()
                    2 -> return SubstractOperation()
                    3 -> return MultiflyOperation()
                    4 -> return DivideOperation()
                }
            }
            else println("1부터 4까지 연산자에 해당하는 숫자를 입력하세요")
        } catch (e: NumberFormatException) {
            println("입력값 오류! 숫자를 입력해주세요.")
        }
    }
}

 

실행 결과

 

이번 개인 과제에서 정말 많은 내용들을 공부할 수 있었고 앞으로 어떻게 어려움들을 헤쳐나가야 할지 어느정도 감이 잡혔다. 이해를 도와주신 장세진 튜터님께 감사의 말씀 올립니다!!

'MyProject' 카테고리의 다른 글

개인 과제 계산기 만들기 Lv4  (0) 2023.11.30
개인 과제 계산기 만들기 Lv3  (0) 2023.11.29
개인 과제 계산기 만들기 Lv2  (0) 2023.11.29
개인 과제 계산기 만들기 Lv1  (0) 2023.11.28

선택 구현 기능

  • Lv4 : AddOperation(더하기), SubtractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스들을 AbstractOperation라는 클래스명으로 만들어 사용하여 추상화하고 Calculator 클래스의 내부 코드를 변경합니다.
    • Lv3 와 비교해서 어떠한 점이 개선 되었는지 스스로 생각해 봅니다.
      • hint. 클래스간의 결합도, 의존성(의존성역전원칙)
//main 안에 op 입력 구현 부분

fun isValidOperator() : AbstractOperation {
    while (true) {
        try {
            var op = readLine()!!.toInt()
            if (op in 1..4) {
                when(op) {
                    1 -> return AddOperation()
                    2 -> return SubstractOperation()
                    3 -> return MultiflyOperation()
                    4 -> return DivideOperation()
                }
            }
            else println("1부터 4까지 연산자에 해당하는 숫자를 입력하세요")
        } catch (e: NumberFormatException) {
            println("입력값 오류! 숫자를 입력해주세요.")
        }
    }
}

 

기존에는 op의 타입과 isValidOperator()의 반환 타입이 Double이었지만 지금은 AbstractOperation이라는 추상 클래스로 바뀐 점이 주목할 부분이다.

class Calculator {

    fun calculator(num1: Double, num2: Double, op: AbstractOperation): Double {
        return when (op) {
            is AddOperation -> {op.operation(num1,num2)}
            is SubstractOperation -> {op.operation(num1,num2)}
            is MultiflyOperation -> {op.operation(num1,num2)}
            is DivideOperation -> {op.operation(num1,num2)}
            else -> throw IllegalArgumentException("잘못된 연산자 입력값입니다.")
        }
    }
}

 

Operation 클래스들을 초기화해서 호출 하는 부분이 전부 날아갔다. op에 들어오는 클래스에 따라 그 클래스에 맞는 operation을 진행해준다. is 연산자를 통해 자식 클래스인 연산클래스들을 다운캐스팅해주어야 한다.

abstract class AbstractOperation {
    abstract fun operation(num1:Double, num2:Double) : Double
}

class AddOperation : AbstractOperation() {
    override fun operation(num1:Double, num2:Double) :Double = (num1 + num2)
}

class SubstractOperation : AbstractOperation() {
    override fun operation(num1:Double, num2:Double) :Double = (num1 - num2)
}

class MultiflyOperation : AbstractOperation() {
    override fun operation(num1:Double, num2:Double) :Double = (num1 * num2)
}

class DivideOperation : AbstractOperation() {
    override fun operation(num1:Double, num2:Double) :Double = (num1 / num2)
}

 

 

들어가기에 앞서 이번 과제의 목표는 의존성 역전 원칙( Dependency Injectoin Principle ) 을 이해하는 것이다.

의존성 역전 원칙이란 객체는 저수준 모듈 보다 고수준 모듈에  의존해야 한다는 것이다. 

 

지금 이해를 토대로 간단히 말하자면, 변하기 쉬운 코드에 의존하는 것(저수준 모듈 : 메인클래스, 객체 등)이 아닌 거의 변하지 않는 개념(고수준 모듈: 추상 클래스, 인터페이스)에 의존해야 한다는 것이다.

 

더 쉽게 말해보자면, 객체의 상속은 인터페이스 또는 추상 클래스을 통해 이루어져야 한다.

즉, 그냥 클래스 직접 상속받지 마라!! 인터페이스나 추상 클래스 만들어서 상속받아라!! 이다.

 

그렇다면 왜 그렇게 해야하느냐??

지금 하고 있는 계산기를 예시로 생각해보자

abstract class AbstractOperation 추상클래스 그리고 그 안에 abstract fun operation 추상 메소드를 만들어 줌으로써 이 클래스를 상속받는 모든 클래스들은 미완성된 추상 메소드를 오버라이딩하여 기능을 완성하게끔 강제한다.

 

이를 통해 클래스간의 조직화가 가능해졌고 존재의 이유를 명확하게 할 수 있다. 또한 다른 기능의 클래스(예를 들어 제곱을 해주는 연산)가 생겼을 때 추가적으로 수정해야할 부분은 calculator 함수에 밑에 코드 한 줄이면 된다.

is SquareOperation -> {op.operation(num1,num2)}

 

변경 사항에 대처하는 것이 유연해져서 유지 보수가 간결해지고 확장성이 용이하다.

+ Recent posts