24-05-17 Kotlin 범위 지정 함수
오늘 오전에는 스프링 개인 과제를 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. 오늘 배운 것
- 네트워크 기본 지식, 알고리즘 문제 풀이 관련 지식, 데이터 모델링에 관한 것들에 대해 공부해보았다.\
- 새로운 코틀린 함수도 하나하나 알아가는 중이다.