코틀린/코딩테스트

코딩테스트 [나머지가 1이 되는 수 찾기]

정혜현 2024. 6. 28. 10:36

문제

자연수 n이 매개변수로 주어집니다. n을 x로 나눈 나머지가 1이 되도록 하는 가장 작은 자연수 x를 return 하도록 solution 함수를 완성해주세요. 답이 항상 존재함은 증명될 수 있습니다.

 

 

 

링크 : https://school.programmers.co.kr/learn/courses/30/lessons/87389


 

풀이

1. 접근

1.1 관찰

나머지가 1인 수 중에 최솟값을 찾는 문제

입력 : 정수

출력 : 정수

 

1.2 계획 

조건 : n % x == 1 

 

 

 

 

 

 

2. 시행착오

2.1 시도

class Solution {
    fun solution(n: Int): Int {
        var answer: Int = 0
        for(i in 1..n) {
        if(n % i == 1) {answer = i 
            break}
            else answer += i}
        return answer
    }
}

 

검증결과 : 성공

 

 

 

 

 

 

 

 

 

for문이 나왔으니 filter를 써봐야겠다. for문 대신 범위 표현식을 쓰고 if문 대신 람다식을 쓰고 최솟값을 구하도록 min()을 써보자.

class Solution {
    fun solution(n: Int): Int = (1..n).filter{n % it == 1}.min()
}

 

검증결과 : 실패. min()을 참조할 수 없다는 오류

/Solution.kt:2:60: error: unresolved reference: min
    fun solution(n: Int): Int = (1..n).filter{n % it == 1}.min()
                                                           ^

원인분석 : min()

https://hhyun-s2.tistory.com/84 여기에서는 max()에서 동일한 문제를 겪었는데 이유가 궁금해졌다. 

 

min() max()의 변화과정

 

1. Nullable (코틀린 이전버전) : 최솟값 또는 최댓값이 존재하지 않을 경우 Null일 수 있다는 모호함으로 불안정했다.

2. Deprecated(코틀린 1.4.0 버전) : 이렇게 리시버가 비어있을 경우 Null을 반환하도록 min() max() 대신 minOrNull() maxOrNull()을 쓰도록 했다. 

3. Non-Nullable(코틀린 1.7.0 버전) : 다시 min() max()를 살렸지만 이제 Null을 엄격하게 허용하지 않고 예외처리 하도록 바꿨다.

 

 

 

 


 

Null 때문에 변화한 건 이해됐는데 Null이 뭐기 때문에 왜 이런 변화가 필요한건지는 모르는 상태라 Null을 알아봐야겠다. 

 

Null : 존재하지 않는 값. 유효하지 않은 객체

Null 참조(Null reference) 또는 Null 포인터(Null pointer) : 유효한 객체를 참조하지 않고 있음을 가리키기 위해 저장된 값

 

Null객체지향 언어의 참조를 위한 시스템을 구축하던 Tony Hoare가 Null pointer를 만들면서 등장했다. 완벽하게 안전한 참조의 사용을 보장하는 것이 목표였지만 null pointer를 넣으면 구현이 쉬워 목표에 반한 도입을 하고만다. 결국 무수한 에러, 불안정함, 시스템 충돌을 낳으며 10억달러짜리 실수라 표현했다.

출처: https://zorba91.tistory.com/339 

 

NPE NullPointerException : 존재하지 않는 Null을 참조할 때 발생하는 예외. 존재하지 않는데 찾아가려고 하니 오류가 발생하므로 프로그램이 비정상 종료된다. 


 

 

 

 

 

그러면 다시 변화과정으로 돌아가서, OrNull을 쓴다해도 Null이 담길 수 있는 건 마찬가지인데 굳이 바꾼 이유는 뭘까?

우선 코드 이름에서 명시적으로 Null을 경고해줄 수 있고, OrNull이 없는 것과 달리 Null일 경우와 아닐 경우를 확인해주기 때문에 예외처리를 해줄 수 있다.

 

정리해보면 아예 Null이 들어가지 못하게 하는 방법은 없다. Null로 인해 발생하는 문제인 예외(NPE)를 피하기 위해 예외처리를 해야된다는거고 예외처리를 통해 NPE를 예방해야한다. 

 

 

 

 

 

 

이제 Null의 예외처리가 왜 필요한지 이해했다. 

class Solution {
    fun solution(n: Int): Int = (1..n).filter{n % it == 1}.minOrNull()!! 
}

 

class Solution {
    fun solution(n: Int): Int = (1..n).filter{n % it == 1}.minOrNull() ?: 0
}

 

Null이 아니라고 단언해주거나 Null일 경우 엘비스연산자로 처리해주면 된다. 아직 의문은... 1.7.0 버전에서 min()이 Non-Nullable로 바뀌었다면, 입력값은 3, 11로 정수라 null도 아닌데 왜 참조를 못할까?

 

2024. 07. 01 

튜터님께 질문하여 해결되었다. 

Q1. min()쓰지 못하는 이유?

A1. 코딩테스트 우측상단 - 컴파일 정보를 확인. 1.7.0 이하의 버전이다.

Q2. it은 요소를 가리키는 게 아닌가? 인덱스를 가리킬 수도 있나?

A2. 요소를 가리키는 게 맞다. 5개의 요소를 넣었으니 0부터 0,1,2,3,4로 5개가 들어있는거다.

 

minOf로 최솟값을 구하는 게 제일 깔끔한데 실제 사용에서는 Null 안전성을 위해 minOrNull()를 써야겠다.

class Solution {
    fun solution(n: Int): Int = (1..n).filter{n % it == 1}.minOf{it}
}

 

 

 

 


회고

 

 

 

 

의문이 꼬리에 꼬리를 물면 최신 의문이 해결되야 최초 의문도 해결돼서 Null까지 들어가게 된 회차. 문법 심화강의에서 예외처리를 듣긴 했으나 그때는 잘 와닿지 않았고 프로젝트를 할 때도 아예 고려하지 않았었다. 오늘에서야 필요성을 인지하게 돼서 앞으로는 코드를 작성할 때 예외처리의 구현도 해줘야겠다.