[Kotlin] List 관련 Operations

RID·2024년 5월 7일
0

배경


알고리즘 문제를 Kotlin으로 하기 시작 한 후, 여러 collection들을 활용해 조금 복잡하게 처리하다 보면 extension function들을 제대로 공부 해야겠다는 생각이 든다.

길게 썼던 코드도 다른 사람의 코드를 보면 정말 짧게 해결되는 경우가 많은데 아직 Kotlin의 이런 확장 함수를 잘 몰라서 그런 것 같다.

이번 기회에 collection별로 사용가능한 함수들이 뭐가 있는지 한 번씩 공부해보고자 한다. 공식문서를 참고하여 오늘은 List에서 쓸 수 있는 것들을 알아보자.

https://kotlinlang.org/docs/list-operations.html

1. Index로 element 가져오기


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를 반환한다.

2. List의 일부분 가져오기


특정 리스트의 n번째부터 m번째까지만 필요해! 라고 하는 경우 사용할 수 있는 함수이다.

val numbers = (0..13).toList() 
println(numbers.subList(3, 6))
// [3, 4, 5]

subList의 경우 두 개의 인자를 받을 수 있는데, 첫 번째 인자는 추출하고 싶은 subList의 시작 index, 그리고 두 번째 인자는 추출하고 싶은 마지막 index의 다음 index이다.

3. 해당 값의 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중 가장 큰 값을 반환한다.

indexOfindexOfFirst의 경우 차이가 없어보이지만, 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가 반환된다.

4. Write Operations


List를 수정하는 함수들에 대해서 알아보자

Add

이건 사실 가장 많이 사용하는 것들 중 하나이다. 하나의 원소, 혹은 리스트를 기존 리스트에 추가할 때 사용한다. 파이썬의 append와 비슷한 개념이다.

val numbers = mutableListOf("one", "five", "six")
numbers.add(1, "two")
numbers.addAll(2, listOf("three", "four"))
println(numbers)

addaddAll모두 만약 하나의 인자만 넣는다면 가장 마지막에 추가되고, 위의 예시처럼 원하는 index와 함께 값을 넣어준다면 해당 위치에 값을 추가하게 된다.

Update

기존의 값을 바꿀 때 사용하는 함수들이다.

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의 값을 일괄적으로 덮어쓴다.

Remove

특정 index의 원소를 List에서 지우고 싶은 경우 사용하게 된다.

val numbers = mutableListOf(1, 2, 3, 4, 3)    
numbers.removeAt(1)
println(numbers) // [1, 3, 4, 3]

Sort

List의 정렬이 필요할 때 sortedsort함수가 두 개 존재하는 것을 알 수 있다. 이때 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에서 사용할 수 있는 함수들도 시간이 되면 다루어 보도록 하자.

0개의 댓글