Skip to content

List 관련 연산

List는 Kotlin에서 가장 인기 있는 기본 컬렉션 유형입니다. 리스트 요소에 대한 인덱스 접근은 리스트를 위한 강력한 연산 세트를 제공합니다.

인덱스로 요소 추출

리스트는 elementAt(), first(), last()개별 요소 추출에 나열된 다른 모든 일반적인 요소 추출 연산을 지원합니다. 리스트에 특화된 기능은 인덱스를 통한 요소 접근입니다. 따라서 요소를 읽는 가장 간단한 방법은 인덱스로 추출하는 것입니다. 이는 인자로 인덱스를 전달하는 get() 함수나 단축 구문인 [index]를 통해 수행됩니다.

리스트 크기가 지정된 인덱스보다 작으면 예외가 발생합니다. 이러한 예외를 피하는 데 도움이 되는 두 가지 다른 함수가 있습니다.

  • getOrElse()는 컬렉션에 인덱스가 존재하지 않을 때 반환할 기본값을 계산하는 함수를 제공할 수 있게 해줍니다.
  • getOrNull()은 기본값으로 null을 반환합니다.
kotlin

fun main() {
    val numbers = listOf(1, 2, 3, 4)
    println(numbers.get(0))
    println(numbers[0])
    //numbers.get(5)                         // exception!
    println(numbers.getOrNull(5))             // null
    println(numbers.getOrElse(5, {it}))        // 5
}

리스트 부분 추출

컬렉션 부분 추출에 대한 공통 연산 외에도, 리스트는 지정된 요소 범위를 리스트 형태의 뷰(view)로 반환하는 subList() 함수를 제공합니다. 따라서 원본 컬렉션의 요소가 변경되면 이전에 생성된 서브리스트에도 반영되며, 그 반대도 마찬가지입니다.

kotlin

fun main() {
    val numbers = (0..13).toList()
    println(numbers.subList(3, 6))
}

요소 위치 찾기

선형 검색

모든 리스트에서 indexOf()lastIndexOf() 함수를 사용하여 요소의 위치를 찾을 수 있습니다. 이 함수들은 리스트에서 주어진 인자와 일치하는 요소의 첫 번째 위치와 마지막 위치를 반환합니다. 일치하는 요소가 없으면 두 함수 모두 -1을 반환합니다.

kotlin

fun main() {
    val numbers = listOf(1, 2, 3, 4, 2, 5)
    println(numbers.indexOf(2))
    println(numbers.lastIndexOf(2))
}

조건(predicate)을 받아 일치하는 요소를 검색하는 두 함수도 있습니다:

  • indexOfFirst()는 조건과 일치하는 첫 번째 요소의 인덱스를 반환하거나, 일치하는 요소가 없으면 -1을 반환합니다.
  • indexOfLast()는 조건과 일치하는 마지막 요소의 인덱스를 반환하거나, 일치하는 요소가 없으면 -1을 반환합니다.
kotlin

fun main() {
    val numbers = mutableListOf(1, 2, 3, 4)
    println(numbers.indexOfFirst { it > 2})
    println(numbers.indexOfLast { it % 2 == 1})
}

정렬된 리스트에서의 이진 탐색

리스트에서 요소를 검색하는 또 다른 방법은 이진 탐색(binary search)입니다. 이 방법은 다른 내장 검색 함수보다 훨씬 빠르지만, 리스트가 자연 순서 또는 함수 매개변수에 제공된 다른 순서에 따라 오름차순으로 정렬되어 있어야 합니다. 그렇지 않으면 결과가 정의되지 않습니다.

정렬된 리스트에서 요소를 검색하려면 값을 인자로 전달하여 binarySearch() 함수를 호출하세요. 요소가 존재하면 함수는 해당 인덱스를 반환합니다. 그렇지 않으면 (-insertionPoint - 1)을 반환하며, 여기서 insertionPoint는 리스트가 정렬된 상태를 유지하기 위해 이 요소가 삽입되어야 할 인덱스입니다. 동일한 값을 가진 요소가 둘 이상인 경우 검색은 그중 어떤 인덱스라도 반환할 수 있습니다.

또한 검색할 인덱스 범위를 지정할 수 있습니다. 이 경우 함수는 제공된 두 인덱스 사이에서만 검색합니다.

kotlin

fun main() {
    val numbers = mutableListOf("one", "two", "three", "four")
    numbers.sort()
    println(numbers)
    println(numbers.binarySearch("two"))  // 3
    println(numbers.binarySearch("z")) // -5
    println(numbers.binarySearch("two", 0, 2))  // -3
}

Comparator 이진 탐색

리스트 요소가 Comparable이 아닌 경우, 이진 탐색에 사용할 Comparator를 제공해야 합니다. 리스트는 이 Comparator에 따라 오름차순으로 정렬되어 있어야 합니다. 예제를 살펴보겠습니다:

kotlin

data class Product(val name: String, val price: Double)

fun main() {
    val productList = listOf(
        Product("WebStorm", 49.0),
        Product("AppCode", 99.0),
        Product("DotTrace", 129.0),
        Product("ReSharper", 149.0))

    println(productList.binarySearch(Product("AppCode", 99.0), compareBy<Product> { it.price }.thenBy { it.name }))
}

여기 Comparable이 아닌 Product 인스턴스 리스트와 순서를 정의하는 Comparator가 있습니다. 상품 p1의 가격이 p2의 가격보다 낮으면 p1p2보다 앞섭니다. 따라서 이 순서에 따라 오름차순으로 정렬된 리스트가 있을 때, binarySearch()를 사용하여 특정 Product의 인덱스를 찾을 수 있습니다.

커스텀 Comparator는 리스트가 자연 순서와 다른 순서를 사용할 때도 유용합니다. 예를 들어 String 요소에 대한 대소문자 구분 없는 순서가 있습니다.

kotlin

fun main() {
    val colors = listOf("Blue", "green", "ORANGE", "Red", "yellow")
    println(colors.binarySearch("RED", String.CASE_INSENSITIVE_ORDER)) // 3
}

Comparison 이진 탐색

비교(comparison) 함수를 사용한 이진 탐색을 사용하면 명시적인 검색 값을 제공하지 않고도 요소를 찾을 수 있습니다. 대신 요소를 Int 값으로 매핑하는 비교 함수를 인자로 받아, 함수가 0을 반환하는 요소를 검색합니다. 리스트는 제공된 함수에 따라 오름차순으로 정렬되어 있어야 합니다. 즉, 비교 결과값이 리스트의 한 요소에서 다음 요소로 갈수록 커져야 합니다.

kotlin

import kotlin.math.sign
data class Product(val name: String, val price: Double)

fun priceComparison(product: Product, price: Double) = sign(product.price - price).toInt()

fun main() {
    val productList = listOf(
        Product("WebStorm", 49.0),
        Product("AppCode", 99.0),
        Product("DotTrace", 129.0),
        Product("ReSharper", 149.0))

    println(productList.binarySearch { priceComparison(it, 99.0) })
}

Comparator 및 Comparison 이진 탐색 모두 리스트 범위에 대해서도 수행될 수 있습니다.

리스트 쓰기 연산

컬렉션 쓰기 연산에서 설명된 컬렉션 수정 연산 외에도, 변경 가능한(mutable) 리스트는 특정한 쓰기 연산을 지원합니다. 이러한 연산은 인덱스를 사용하여 요소에 접근함으로써 리스트 수정 기능을 확장합니다.

추가

리스트의 특정 위치에 요소를 추가하려면, 요소 삽입 위치를 추가 인자로 제공하는 add()addAll()을 사용하세요. 해당 위치 뒤에 오는 모든 요소는 오른쪽으로 이동합니다.

kotlin

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

업데이트

리스트는 주어진 위치의 요소를 교체하는 함수인 set()과 연산자 형태인 []도 제공합니다. set()은 다른 요소의 인덱스를 변경하지 않습니다.

kotlin

fun main() {
    val numbers = mutableListOf("one", "five", "three")
    numbers[1] =  "two"
    println(numbers)
}

fill()은 단순히 모든 컬렉션 요소를 지정된 값으로 교체합니다.

kotlin

fun main() {
    val numbers = mutableListOf(1, 2, 3, 4)
    numbers.fill(3)
    println(numbers)
}

삭제

리스트의 특정 위치에서 요소를 삭제하려면 위치를 인자로 제공하는 removeAt() 함수를 사용하세요. 삭제되는 요소 뒤에 오는 요소들의 모든 인덱스는 1씩 감소합니다.

kotlin

fun main() {
    val numbers = mutableListOf(1, 2, 3, 4, 3)    
    numbers.removeAt(1)
    println(numbers)
}

정렬

컬렉션 정렬에서는 컬렉션 요소를 특정 순서로 추출하는 연산을 설명합니다. 변경 가능한 리스트의 경우, 표준 라이브러리는 동일한 정렬 작업을 제자리(in-place)에서 수행하는 유사한 확장 함수를 제공합니다. 리스트 인스턴스에 이러한 연산을 적용하면 해당 인스턴스의 요소 순서가 직접 변경됩니다.

제자리 정렬 함수는 읽기 전용 리스트에 적용되는 함수와 이름이 비슷하지만 ed/d 접미사가 없습니다:

변경 가능한 리스트에서 호출된 asReversed()는 원본 리스트의 반전된 뷰(view)인 다른 변경 가능한 리스트를 반환합니다. 해당 뷰의 변경 사항은 원본 리스트에 반영됩니다. 다음 예제는 변경 가능한 리스트를 위한 정렬 함수를 보여줍니다:

kotlin

fun main() {
    val numbers = mutableListOf("one", "two", "three", "four")

    numbers.sort()
    println("Sort into ascending: $numbers")
    numbers.sortDescending()
    println("Sort into descending: $numbers")

    numbers.sortBy { it.length }
    println("Sort into ascending by length: $numbers")
    numbers.sortByDescending { it.last() }
    println("Sort into descending by the last letter: $numbers")
    
    numbers.sortWith(compareBy<String> { it.length }.thenBy { it })
    println("Sort by Comparator: $numbers")

    numbers.shuffle()
    println("Shuffle: $numbers")

    numbers.reverse()
    println("Reverse: $numbers")
}