오늘 배운 것

24-05-17 Kotlin 범위 지정 함수

무무11 2024. 5. 17. 20:58

오늘 오전에는 스프링 개인 과제를 step 2 까지 마무리하고 리드미 작성까지 모두 마무리지었다. 물론 아직 다음주까지 더 구현해볼 수 있는 것들이 있기 때문에 완전히 끝난 것은 아니지만 어찌되었든 무언가 하나를 매듭지은 느낌이라 좋았다.

 

스프링을 공부하면서 개념을 잘 모른다거나 CS 지식이 부족하여 이해가 어려웠던 부분들이 많았었기 때문에 오후에는 CS 공부를 주로 하면서 시간을 보냈다.

 

시간이 좀 생겨서 알고리즘 공부도 조금 해봤는데 여기에는 그 내용을 조금 적어보려고 한다.

 

풀어본 문제와 문제를 풀면서 알게된 함수 하나를 정리해보았다.

 

1. 알고리즘 문제 풀이

1-1. 풀어본 문제

leetcode에 있는 '42. Trapping Rain Water' 문제를 풀어보았다.

 

[문제]

 

주어진 음수가 아닌 정수 n은 각 막대의 너비가 1인 높이 지도를 나타낸다. 비가 온 후에 얼마나 많은 물이 갇히게 되는지 계산하세요.

 

- 예시 1

입력: height = [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]

출력: 6

설명: 아래의 높이 지도(검은 부분)은 [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1] 배열로 표현된다. 이 경우 여섯 단위의 빗물(파란색 부분)이 갇혀 있게 된다.

 

- 예시 2

입력: height = [4, 2, 0, 3, 2, 5]

출력: 9

 

- 제한조건

  • n == height.length
  • 1 <= n <= 20000
  • 0 <= height[i] <= 100000

 

1-2.  문제 풀이

사실 이 문제의 경우는 제대로 접근하는 것 조차 힘들었다. 투 포인터에 대해서 공부를 해보고 있었고, 그러다가 이 문제를 알고 풀어보게 되었는데 어떻게 투 포인터를 사용해 풀어야하는지 전혀 감을 잡지 못했다.

 

결국 다른 사람의 풀이를 보고 이해하려고 노력해보았고 생각보다 간단하게 풀 수 있다는 것을 알게되었다. 이해하고 나니 참 간단했는데 아이디어를 떠올리는게 너무 어렵다는 생각이 들었다.

 

문제 풀이 아이디어는 다음과 같다.

 

맨 왼쪽 인덱스와 맨 오른쪽 인덱스 양 쪽을 포인터로 잡고 점점 가운데로 이동한다. 이때 두 포인터는 가장 높은 막대를 향해 접근한다.

 

두 포인터 사이의 가상의 가장 높은 막대가 있다고 생각해보자. 그럴 경우에 포인터가 왼쪽 또는 오른쪽으로부터 가장 높은 막대에 접근할 때 여태까지 지나쳐온 곳들의 막대 높이의 최대값보다 작은 값일 경우에는 그 차이 만큼 빗물이 채워질 것이다. 가상의 가장 높은 막대의 높이가 어디인지, 높이가 얼마인지 알 수 없기 때문에 왼쪽, 오른쪽 포인터를 움직일 때 여태까지 두 포인터가 지나쳐온 곳들의 높이의 최대값이 작거나 같은 쪽의 포인터를 한 칸씩 이동시키면 된다.

 

위의 내용을 코드로 작성하면 다음과 같다.

class Solution {
    fun trap(height: IntArray): Int {
        var answer = 0
        var left = 0 // 왼쪽 포인터
        var right = height.size - 1 // 오른쪽 포인터
        // 여태까지 지나쳐온 곳 중 막대 높이의 최대값
        var leftMax = height[left] 
        var rightMax = height[right]

        while (left < right) {
        	// 포인터 움직임에 따라 막대 높이의 최대값 갱신해주기
            leftMax = if(height[left]>=leftMax) height[left] else leftMax
            rightMax = if(height[right]>=rightMax) height[right] else rightMax

            if (leftMax <= rightMax) {
                // 채워진 빗물 칸 수 더해주기
                answer += leftMax - height[left]
                // 포인터를 한 칸 중앙으로 이동
                left += 1
            } else {
                // 채워진 빗물 칸 수 더해주기
                answer += rightMax - height[right]
                // 포인터를 한 칸 중앙으로 이동
                right -= 1
            }
        }

        return answer
    }
}

 

엄청나게 어렵게만 느껴졌는데 간단한 코드로 문제를 풀 수 있다는게 너무 놀라웠다. 이런 문제 풀이 아이디어들을 잘 기억해두었다가 다른 문제에 적용해볼 수 있도록 공부를 해야할 것 같다.

 

그리고 다른 답안을 보다가 처음보는 함수를 발견할 수 있었다.

 

아래 2줄, 2줄의 코드는 같은 역할을 한다.

leftMax = if(height[left]>=leftMax) height[left] else leftMax
rightMax = if(height[right]>=rightMax) height[right] else rightMax

leftMax = height[left].cocerceAtLeast(leftMax)
rightMax = height[right].cocerceAtLeast(rightMax)

찾아보니 이런 함수들을 범위 지정 함수라고 부른다는 것을 알게 되었다. 어려운 내용이 아니기 때문에 간단하게 여기에 정리해보았다.

 

2. Kotlin 범위 지정 함수

코틀린에서는 범위 지정 함수로 corerceIn, cocerceAtMost, coerceAtLeast 이렇게 세 가지 함수를 제공한다.

 

범위 지정 함수란 특정 범위를 지정하여 값이 이 범위 안에 속하도록 만드는 것이다.

 

위와 같이 설명하면 이해가 잘 가질 않는다 직접 코드로 작성해서 확인해보는 것이 이해가 쉽다.

 

먼저 coerceIn 함수는 범위를 인자로 받아서 특정 값이 이 범위 안에 반드시 속하도록 만들어준다.

 

코드로 직접 확인해보자.

fun main() {
    // 값이 반드시 10~100 사이에 있어야 한다.
    // 값을 반드시 지정한 범위 안에 위치시켜야하는데 1과 8은 범위의 최소값인 10보다 작다.
    // 따라서 1과 8을 범위 안으로 끌여들여 최소값인 10이 반환된다.
    println(1.coerceIn(10..100)) // 10
    println(8.coerceIn(10..100)) // 10

    // 범위 안의 수 들은 그대로 출력된다.
    println(10.coerceIn(10..100)) // 10
    println(50.coerceIn(10..100)) // 50
    println(100.coerceIn(10..100)) // 100

    // 범위 최대값인 100보다 큰 수들은 최대값인 100이 반환되게 된다.
    println(10000.coerceIn(10..100)) // 100
    println(100000.coerceIn(10..100)) // 100
}

그리고 coerceAtMost와 coerceAtLeast가 있다.

 

cocerceAtMost는 인자로 수를 하나 받는데 이것은 최대값을 나타내고 이것이 의미하는 것은 -∞ ~ '최대값'  안에 값이 반드시 위치시키도록 만든다는 이야기이다. 인자로 받은 숫자보다 큰 값을 가질 수 없으며, 더 큰 값일 경우에 인자로 받은 숫자가 반환된다.

 

coerceAtLeast는 반대로 인자로 받은 수가 최소값을 나타낸다.

 

코드로 직접 확인해보자.

fun main() {
    // 값이 반드시 -∞ ~ 5 사이에 있어야 한다
    // 따라서 5 보다 큰 수는 5가 되게 된다
    println((-10).coerceAtMost(5)) // -10
    println(0.coerceAtMost(5)) // 0
    println(10.coerceAtMost(5)) // 5


    // 값이 반드시 10 ~ ∞ 사이에 있어야 한다
    // 따라서 10 보다 작은 수는 10이 되게 된다
    println((-20).coerceAtLeast(10)) // 10
    println(0.coerceAtLeast(10)) // 10
    println(100.coerceAtLeast(10)) // 100
}

coerceAtMost와 coerceAtLeast는 다음과 같이 활용할 수 있다. 위의 문제 풀이 정리 맨 마지막에 적은 코드가 바로 이것이다.

// b가 a보다 크면 a에 b가 대입된다 (같거나 작으면 그대로)
a = b.coerceAtLeast(a)

// b가 a보다 작으면 a에 b가 대입된다 (같으면 크면 그대로)
a = b.coerceAtMost(a)

 

 

2. 오늘 배운 것

- 네트워크 기본 지식, 알고리즘 문제 풀이 관련 지식, 데이터 모델링에 관한 것들에 대해 공부해보았다.\

- 새로운 코틀린 함수도 하나하나 알아가는 중이다.