과제의 볼륨이 프로젝트급이 아니고 강의를 중점으로 한 복습이 핵심이기 때문에 강의를 중점적으로 들으며 응용할 내용을 찾고 공유를 준비했다.
이번 중간 점검 성격의 코드 리뷰가 잘 진행되면 예제는 간단한 계산기지만 조금 더 응용해서 배운 내용 안에서 더 써먹을 수 있는 기능도 추가해보려고 몇 개 메모를 해뒀다.
- 숫자 따로 연산자 따로 받지 말고 식을 한 번에 받아 처리하기 (입력을 나누지 말고
3 + 5
로 받기)- 숫자와 연산자를 늘려 더 긴 식을 한 번에 계산하기 (반복문 활용)
- Opeartion 추상화 하고 operate 함수 하나로만 관리하기
- 생성자 활용하기
- 입력과 연산에서 예외처리 하기
- 형 변환해서 써보기, 음수 처리도 문제 없는지 작성하기
과제는 간단한 사이즈를 요구했지만 이것중 몇개만 더 추가 가능해도 노베이스로 시작하신 분들이 하기엔 응용 난이도가 꽤 높으니 과제를 초과달성했다고 생각한다.
물론 진도가 맞지 않으면 일방적인 공유는 지양하려고 하고 이 간단한 CLI 계산기의 작동 구조를 알고 코딩할 수 있으면 진행할 것 같다. (GUI는 나도 못해서 찾아보지도 않았다.)
여담으로 나는 진도를 맞춰서 강의에 나온 예제를 등록해두고 질문이 오면 화면 공유로 같이 풀어 나가보는 식으로 응용도 해보며 해결해드리고 있는데 덕분에 이해가 잘 된다고 말씀해주시긴 하지만 나는 내 말솜씨를 믿는 편이 아니라 잘못 알려드렸거나 넘어가신게 아닐까 걱정되기도 했다.
위에서 언급한 대로 오늘 강의 진도와 과제에 맞춰서 숫자 두 개에 연산이 가능한 계산기 기초 기능만큼 작동하는 Level1, 2를 작성하고 리뷰를 해보기로 했다. (일단 과제의 목표에선 Class로 기능을 빼야한다.)
다들 Kotlin을 접하신 지 겨우 이틀 째인지라 한 방에 클래스까지 작성해서 작동시키기엔 역시나 어려웠고 이걸 설명하는 내 미숙함도 공존해서 약간은 아쉬운 코드 리뷰 시간이 되었다.
화면 공유로 최대한 많은 응용과 코드 수정을 통해서 코드의 동작 이유를 설명드리고 나중에 자기 코드에서 바꾸실 때도 편한 기능들을 많이 보여주며 진행했는데 어찌저찌 진행이 되나 싶었지만 연산 기능을 싸그리 Class로 빼서 설명하는 부분에서는 다들 머리가 아프신 것 같아 빠르게 넘겼다.
아무래도 아직 한 번 보셨을 강의를 응용해 계속 써먹어봐야 익숙해지는 부분인 데다가 당장 멀쩡히 돌아가는 몇 줄짜리의 연산 기능을 Class로 작성하는 이유를 설명하기에도 기능의 규모가 작다보니 와닿기가 힘든 점이 많이 이해가 됐다.
다른 시점에선 내가 엄청나게 헤매던 고등학교 학창시절의 경험이 있기에 오늘 리뷰를 마치기라도 했나 싶긴 하다. 이틀 차만에 Git도 써보고 Kotlin 기본 문법 상으로도 이미 진도를 꽤 많이 뺀 거나 다름 없고 Class로 짜기만 하면 Level 1, 2, 3중 2개를 물리치신 것이니 남은 3일간의 일정에 도움이 되셨으면 좋겠다.
(위에 추가로 메모해둔 부분은 리뷰 해보고나니 지금 섞어서 리뷰하기엔 팀원분들의 부담을 너무 가중시키는 것 같아 개인적으로 작성하기로 했다.)
명함 지갑을 만드는 회사에서 지갑의 크기를 정하려고 합니다. 다양한 모양과 크기의 명함들을 모두 수납할 수 있으면서, 작아서 들고 다니기 편한 지갑을 만들어야 합니다. 이러한 요건을 만족하는 지갑을 만들기 위해 디자인팀은 모든 명함의 가로 길이와 세로 길이를 조사했습니다.
fun solution(sizes: Array<IntArray>): Int {
var sizeAlignedList = sizes.map { size ->
if (size[0] < size[1]) intArrayOf(size[1], size[0]) else size
}
var maxWidth = sizeAlignedList.map { it[0] }.maxOrNull() ?: Int.MIN_VALUE
var maxHeight = sizeAlignedList.map { it[1] }.maxOrNull() ?: Int.MIN_VALUE
return maxWidth * maxHeight
}
문제의 해답을 구하는 방식은 금방 떠올랐지만 이걸 간결하게 구현하는 방법을 고민하느라 꽤 오랜 시간이 걸렸다. 특히 위 코드를 완성시키고 단일 표현식으로 표현하기 위해 온갖 삽질을 했는데 그 결과가 이렇다.
// 버그 덜 잡아서 에러남
fun solution(sizes: Array<IntArray>): Int = sizes.sortedBy { it[0] }.map { it[0] to it[1] }
.let { widths, heights ->
(widths.maxOrNull() ?: Int.MIN_VALUE) * (heights.maxOrNull() ?: Int.MIN_VALUE)
}
다 풀고나서 보면 정말 끔찍한 코드다. 단일 표현식에 매몰돼서 억지로 줄이고 바꾸고 정렬하고 온갖 개판을 쳐놨는데 일단 Swift에서 Tuple을 좋아했던만큼 Pair를 어떻게든 채용해서 가로 세로를 구별하려던게 목표였고 어쨌든 결과적으론 읽기도 어렵고 실행마저 안되는 구닥다리 코드가 됐다.
제출을 끝내고 나서 점수부터 불안하긴 했는데 역시나 훨씬 간단하게 각 최대값에 접근할 수 있는 방법이 있었다.
val widths = mutableListOf<Int>()
val heights = mutableListOf<Int>()
sizes.forEach {
widths.add(Math.max(it[0], it[1]))
heights.add(Math.min(it[0], it[1]))
}
return widths.maxOrNull()!! * heights.maxOrNull()!!
이렇게 아주 정렬에 목이 말랐는데 적은 순회로 그냥 값을 미리 나누어 저장하는 방식은 간단하고 빠른 퍼포먼스를 보장했을테고
fun solution3(sizes: Array<IntArray>): Int
= sizes.map{it.maxOrNull()!!}.maxOrNull()!! * sizes.map{it.minOrNull()!!}.maxOrNull()!!
내가 목 멘 단일표현식에 순회를 돌릴거면 화끈하게 각각의 값에서 값을 찾아와 한 방에 반환해주는 방법도 있었다.
여러모로 단일표현식에 매몰되기도 했고 명함의 좌우를 돌려서 정리하기 편한 상태로 값을 정렬한다는 강박감에 사로잡혀서 변수 관리를 더 하고 퍼포먼스를 많이 희생시켰고 간결함보다 복잡함이 눈에 띄는 코드를 작성해서 좀 속상하다. 할 거였으면 차라리 진짜 유지보수가 편했으면 몰랐는데 두 마리 토끼를 다 놓친 것 같다.
코딩 테스트식 알고리즘에 뇌가 녹았나 싶기도 해서 다음 몇 차례의 문제는 원래 스타일대로 유지보수가 편한 방식을 택해보려고 한다.
결론은 복잡하게 생각할 것 없이 큰 값은 큰 값이고 작은 값은 작은 값이다.
선생님 다른 식견에 감탄을 하고 갑니다