알고리즘 문제를 Kotlin으로 하기 시작 한 후, 여러 collection
들을 활용해 조금 복잡하게 처리하다 보면 extension function들을 제대로 공부 해야겠다는 생각이 든다.
길게 썼던 코드도 다른 사람의 코드를 보면 정말 짧게 해결되는 경우가 많은데 아직 Kotlin의 이런 확장 함수를 잘 몰라서 그런 것 같다.
이번 기회에 collection
별로 사용가능한 함수들이 뭐가 있는지 한 번씩 공부해보고자 한다. 공식문서를 참고하여 오늘은 List
에서 쓸 수 있는 것들을 알아보자.
List
를 쓰다보면 가장 많이 만날 수 있는 상황이 바로 IndexOutOfBoundsException
이다. 말 그대로 List
의 마지막 index
보다 큰 값으로 참조하려고 하면 발생하는 에러다. 예를 들어 아래와 같은 상황에서 발생한다.
val numbers = listOf(1, 2, 3, 4)
println(numbers.get(5)) // Exception!!
List의 마지막 index는 3인데, 5번째 원소를 가져오려고 하니 Exception
이 throw된다. 이 때는 아래와 같은 함수들을 사용해보자.
val numbers = listOf(1, 2, 3, 4)
println(numbers.getOrNull(5))
println(numbers.getOrElse(5,{it})) // 5
getOrNull(index)
: 만약 해당 index
로 참조할 수 없는 경우 null값을 반환한다.getOrElse(index,{defaultValue})
: 만약 해당 index
로 참조할 수 없는 경우 defaultValue
를 반환한다. 특정 리스트의 n번째부터 m번째까지만 필요해! 라고 하는 경우 사용할 수 있는 함수이다.
val numbers = (0..13).toList()
println(numbers.subList(3, 6))
// [3, 4, 5]
subList
의 경우 두 개의 인자를 받을 수 있는데, 첫 번째 인자는 추출하고 싶은 subList의 시작 index, 그리고 두 번째 인자는 추출하고 싶은 마지막 index의 다음 index이다.
구현을 하다보면 List
내에서 특정 값의 index
가 필요한 경우가 존재한다. 이때는 indexOf
라는 함수를 활용하여 해당 index
를 O(n)의 시간복잡도로 가져올 수 있다.
val numbers = listOf(1, 2, 3, 4, 2, 5)
println(numbers.indexOf(2)) // 1
println(numbers.lastIndexOf(2)) // 4
indexOf
: 해당 값과 같은 index
중 가장 작은 값을 반환한다.
indexOfLast
: 해당 값과 같은 index
중 가장 큰 값을 반환한다.
아래와 같은 방식으로 단순히 값만 비교하는 것이 아니라 조건을 비교할 수도 있다.
val numbers = mutableListOf(1, 2, 3, 4)
println(numbers.indexOfFirst { it > 2}) // 2
println(numbers.indexOfLast { it % 2 == 1}) // 2
indexOfFirst
: 해당 조건을 만족하는 index
중 가장 작은 값을 반환한다.
indexOfLast
: 해당 조건을 만족하는 index
중 가장 큰 값을 반환한다.
indexOf
와 indexOfFirst
의 경우 차이가 없어보이지만, indexOf
의 경우 뒤에 {}
사이에 조건을 넣는 방식으로 검색이 불가능하다.
위의 세 가지 함수 모두 해당 값이나 조건을 만족하지 않는 경우 -1
을 반환한다.
만약, List
가 정렬되어 있는 경우 위의 O(n)의 시간복잡도보다 좋은 O(logN)의 binary search를 사용할 수도 있다.
val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort() // ["four","one","three","two"]
println(numbers)
println(numbers.binarySearch("two")) // 3
println(numbers.binarySearch("z")) // -5
println(numbers.binarySearch("two", 0, 2)) // -3 이 경우 0번째부터 2번째 인덱스 까지만 search한다
binarySearch
의 경우 해당 값이 존재하는 경우 해당 index
를 반환하지만, 그렇지 않은 경우 -insertionPoint - 1
의 값을 반환한다. 여기서 insertionPoint
의 경우 만약 해당 값이 존재했다면 있어야할 index
를 의미한다.
z
의 경우 존재했다면 4번 index
에 존재해야하고, 그렇기 때문에 -5가 반환된다.
List를 수정하는 함수들에 대해서 알아보자
이건 사실 가장 많이 사용하는 것들 중 하나이다. 하나의 원소, 혹은 리스트를 기존 리스트에 추가할 때 사용한다. 파이썬의 append
와 비슷한 개념이다.
val numbers = mutableListOf("one", "five", "six")
numbers.add(1, "two")
numbers.addAll(2, listOf("three", "four"))
println(numbers)
add
와 addAll
모두 만약 하나의 인자만 넣는다면 가장 마지막에 추가되고, 위의 예시처럼 원하는 index
와 함께 값을 넣어준다면 해당 위치에 값을 추가하게 된다.
기존의 값을 바꿀 때 사용하는 함수들이다.
val numbers1 = mutableListOf("one", "five", "three")
numbers1[1] = "two"
println(numbers1) // [one, two, three]
val numbers2 = mutableListOf(1, 2, 3, 4)
numbers2.fill(3)
println(numbers2) // [3, 3, 3, 3]
일반적으로 []
를 사용해서 변경하는 경우 해당 index
의 값을 수정할 수 있고, fill
을 사용하면 모든 index
의 값을 일괄적으로 덮어쓴다.
특정 index
의 원소를 List
에서 지우고 싶은 경우 사용하게 된다.
val numbers = mutableListOf(1, 2, 3, 4, 3)
numbers.removeAt(1)
println(numbers) // [1, 3, 4, 3]
List
의 정렬이 필요할 때 sorted
와 sort
함수가 두 개 존재하는 것을 알 수 있다. 이때 ed/d
의 suffix가 붙지 않은 경우에는 해당 List
를 직접적으로 정렬하는 것이고, ed/d
가 붙은 경우 정렬한 새 List
를 반환하는 함수이다.
여기서는 List
의 수정을 다루고 있으니 ed/d
가 붙지 않은 함수들에 대해서 살펴보자.
val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort()
println("오름차순 정렬: $numbers")
numbers.sortDescending()
println("내림차순 정렬: $numbers")
numbers.sortBy { it.length }
println("length로 오름차순 정렬: $numbers")
numbers.sortByDescending { it.last() }
println("마지막 문자로 내림차순 정렬: $numbers")
numbers.sortWith(compareBy<String> { it.length }.thenBy { it })
println("Comparator를 활용해서 정렬: $numbers")
numbers.shuffle()
println("Shuffle: $numbers")
numbers.reverse()
println("Reverse: $numbers")
위의 네 함수는 이름에서 직관적으로 해당 역할을 알 수 있다. 나머지 함수들을 살펴보자.
-sortWith
: 비교가 명확하지 않은 경우(인스턴스 등) Comparator를 만들어 누가 더 큰지에 대한 정보와 함께 정렬을 할 수 있다. 위의 경우 length
로 비교하였다.
shuffle
: 말 그대로 해당 List
를 마구잡이로 섞는다.reverse
: 해당 List
의 순서를 반대로 뒤집는다. 오늘 되게 간단한 List
함수들에 대해서 살펴보았는데, 역할과 이름을 정확히 알고 사용하는 것과 단순히 그냥 사용하는 것에 큰 차이가 있음을 느낄 수 있었다. sort
의 경우 과거형과 아닌 것의 차이를 알지 못했는데, 과거 시제를 사용한 이유가 생각보다 꽤 직관적인 것을 이제야 느낀다..
나머지 Map
, Set
에서 사용할 수 있는 함수들도 시간이 되면 다루어 보도록 하자.