오늘도 연습삼아 프로그래머스에서 문제를 풀어보다가 재밌는 사실들 몇 가지를 알게되어서 정리해서 적어본다
1. 풀어본 문제
1-1. 문제
프로그래머스에 있는 연습문제 '수박수박수박수박수박수?' 문제를 풀어보았다.
[문제] 길이가 n이고, "수박수박수박수박수...."와 같은 패턴을 유지하는 문자열을 리턴하는 함수, solution을 완성하세요. 예를들어 n이 4이면 "수박수박"을 리턴하고 3이라면 "수박수"를 리턴하면 됩니다.
- 제한 조건: n은 길이 10000이하인 자연수입니다.
입출력 예시
n | return |
3 | "수박수" |
4 | "수박수박 |
1-2. 내가 푼 풀이
어떻게 풀어야할까 고민한 결과 여러 방법이 떠올랐는데 다소 무식하고 비효율적이게 보일지라도 정말 쉽게 해결하는 방법이 딱 떠올랐다. 조건에 따라 그에 맞는 문자열을 생성하는게 아니라, 최대길이가 10000이라고 주어져있으니 10000개로 된 문자열을 만들고 거기서 n개 만큼의 문자열을 뽑아오자라는 생각이었다.
class Solution {
fun solution(n: Int) = "수박".repeat(5000).substring(0,n)
}
"수박"을 5000번 반복하면 문자 10000개자리 "수박수박수박..."이 생성되고 거기서 n개만큼 문자열을 추출하면 끝이다.
1-3. 다른 풀이를 찾아보기
내가 푼 풀이는 제한 조건의 숫자가 크면 클수록 점점 더 비효율적이되는 풀이라는 생각이 들었다. 필요한 문자의 수 만큼 문자열을 만들어주는 다른 사람들의 풀이를 찾아보았다.
class Solution {
fun solution(n: Int) = String(CharArray(n) {i-> if(i%2==0) '수' else '박'})
}
개인적으로 가장 마음에 들었던 풀이가 이것이었다. 코드의 구성을 보면 먼저 n개의 문자로 이루어진 배열(CharArray)을 생성하게 되는데 배열에는 인덱스의 짝수, 홀수 여부에 따라 '수' 또는 '박'이 들어가게 된다. 그리고 이것을 String의 생성자를 통해 문자열로 출력하게 된다.
깔끔하고 간결한 풀이라서 좋은것 뿐만 아니라 내가 작성한 코드와 비교도 할 수 없을 정도로 실행 속도가 빨랐다. 내가 작성한 코드는 실행시간이 8,9,10 ms에 이르는 반면 이 코드는 실행시간이 0.02, 0.03 ms 정도밖에 되지 않았다.
그리고 CharArray()가 아니라 Array<Char>()를 이용해도 똑같은 게 아닐까 라는 생각에 코드를 다시 짜봤다.
class Solution {
fun solution(n: Int) = Array(n) { i-> if(i%2==0) '수' else '박'}.joinToString("")
}
타입 추론이 가능하기 때문에 <Char>는 생략할 수 있다. 이렇게 했더니 실행시간이 20ms가 훌쩍 넘어갔다. 왜 이 둘은 실행속도가 이렇게 크게 차이가 나는 것인지, CharArray와 Array<Char>는 둘다 문자로 이루어진 배열로 똑같은 것이 아닌지 궁금증이 생겨서 이 부분에 대해 조금 더 찾아보기로 했다.
2. CharArray와 Array<Char>의 차이
Kotlin에서는 원시 타입과 참조 타입을 구분하지 않고 항상 같은 타입을 사용한다는 것은 거의 맨 처음에 배웠던 것 같은데 이것이 무슨 내용인지 자세히 알지 못하고 지나쳤었는데 이제는 확실히 알고 넘어가야 할 것 같다. 찾아본 내용을 여기 정리해본다.
Kotlin에서는 모든 대상을 객체처럼 취급할 수 있고, Int, Double 등 과 같이 Java에서는 원시 타입이었을 값에 대해서도 편하게 메서드 호출을 할 수 있고, 해당 값이 사용되는 문맥에 따라 컴파일러가 알아서 원시 타입 값으로 처리하거나 객체로 처리해준다. 컴파일러단에서 알아서 효율적으로 처리되기 때문에 개발자 입장에서는 편리하다.
그렇지만 위와 같은 이유 때문에 Array<Int>와 같은 배열의 경우 배열 내부에 저장할 값이 원시 타입의 32비트 정수 값으로만 쓰일지 아닐지 여부를 알 수 없기때문에, 이러한 경우 컴파일러는 가장 안전한 방식을 택해야 하므로 배열의 원소로 32비트 정수 값이 아니라 정수 값 객체를 가리키는 참조를 사용할 수 밖에 없다.
arrayOf(1,2,3,4) 과 같이 배열을 생성한다는 것은 Int 객체를 가리키는 참조 4개가 들어있는 배열 객체가 생기고 각각 1,2,3,4의 값이 들어 있는 Int 타입 객체가 생긴다는 뜻이다. 배열 자체에는 객체를 가리키는 참조만 들어가고 실제 객체는 힙 안의 다른 위치에 저장된다.
다시 말해 CharArray, IntArray, DoubleArray, intArrayOf 등으로 만든 배열은 원시 타입 값을 연속적으로 저장한 원시배열이고, Array<Int>, Array<Char>, arrayOf 등으로 생성한 배열은 참조를 통해 간접적으로 객체에 접근해야하는 참조배열인 것이다.
원시 타입은 JVM의 메모리 공간의 스택 영역에 저장되는 반면, 참조 타입은 스택 영역에 참조 값만 있고 실제 값은 힙 영역에 존재하게 된다. 참조 타입은 최소 2번 메모리 접근을 해야하고, 일부 타입의 경우 값을 필요로 할 때 참조 타입에서 원시 타입으로 변환하는 과정까지 거쳐야 하기 때문에 원시타입과 비교해서 접근 속도가 매우 느리다. 또한 참조 타입이 원시 타입에 비해 사용하는 메모리 양이 압도적으로 높다.
둘다 어떤 방식으로 코드를 작성하던 결과는 똑같이 정상적으로 출력되니 생긴것도 비슷하고 사실상 똑같은 거 아닌가하고 그냥 넘어갈 수도 있었겠지만 역시 찾아보길 잘했다는 생각이 들었다. 이제 막 걸음마를 뗀 단계이다 보니 어떻게든 작동이 되는 코드를 짜는 것에 중점을 두고 있지만, 어떤 것이 더 효율적인 방식인지에 대해서도 고민해가면서 공부를 해야겠다는 생각이 들었다.
또 더 효율적인 코드를 짜려면 CS 공부도 게을리하지 말아야한다는 얘기도 많이 들었다. 앞으로 CS 공부도 더 열심히 해야할 것 같다.
3. 오늘 배운 것
- 원시 타입, 참조 타입 그리고 원시 배열, 참조 배열에 대해서 알게 되었다.
- 매일 조금씩 하고 있는 CS 공부를 더 열심히 해야겠다.
'오늘 배운 것' 카테고리의 다른 글
24-03-21 Kotlin 컬렉션 필터링하기 filter(), filterIndexed() ... (0) | 2024.03.21 |
---|---|
24-03-20 Kotlin Pair와 Zip (2) | 2024.03.20 |
2024-03-18 오늘 배운 것 (2) | 2024.03.18 |
2024-03-17 Kotlin 배열을 문자열로 출력하기 (2) | 2024.03.17 |
24-03-16 Kotlin 이중 반복문 빠져나가기 (2) | 2024.03.16 |