개발/코틀린

(코틀린) 컬렉션

DinoDev 2022. 10. 30. 20:20
728x90
반응형

컬렉션

요소들을 저장하기 위해 설계된 클래스 입니다.

kotlin 표준 라이브러리는 다양한 컬렉션 라이브러리와 컬렉션 안에 데이터를 조작하기 위한 종합적인 api를 제공합니다.

컬렉션을 조작하는 모든 연산이 inline function이라서 함수 호출마다 부가 비용이 들지 않습니다.

컬렉션 타입

컬렉션 타입은 array, iterable, sequence, map으로 4가지로 분류 됩니다.

이터러블

이터러블은 즉시 계산되는 상태가 있는 컬렉션을 표현합니다. 실제로 값을 저장하고 있는 컬렉션 입니다.

원소를 순회할 수 있는 iterator()라는 메소드를 제공하고 for를 이용해서 순회할 수 있습니다.

fun main() {
    val list = listOf("red", "yellow", "green")
    for (item in list) {
        print("$item ")
    }
}

가변 이터러블을 사용하면 기존에 만든 이터러블에 원소를 추가 할 수 있습니다.

fun main() {
    val list = ArrayList<String>()
    list.add("abc") // 컬렉션에 데이터 추가
    list = ArrayList<String>() // list는 val이기 때문에 list 자체를 바꿀 수 없음
}

불변 컬렉션 타입에서 유용한 특징으로는 공변성(covariance)이 있습니다. 공변성이라는 말은 T가 U의 하위 타입인 경우 Iterable<T>가 Iterable<U>의 하위 타입이라는 뜻입니다. 하지만 가변 컬렉션인 경우는 동작하지 않습니다.

fun processCollection(c: Iterable<Any>) {

}

fun processMutableCollection(c: MutableCollection<Any>) {
    c.add(123)
}

fun main() {
    val list = listOf("a", "b", "c")
    processCollection(list) // List<Any>로 전달

    val mutableList = arrayListOf("a", "b", "c")
    processMutableCollection(mutableList) // 에러 발생
}

컬렉션, 리스트, 집합

이터러블 하위 분류 중에는 Collection 인터페이스로 표현되는 타입들과 Collection 타입의 하위 타입인 MutableCollection 인터페이스로 표현되는 타입들이 있습니다. 

  • List는 인덱스를 통한 원소 접근이 가능하며 순서가 정해져 있는 컬렉션 입니다.
    • ArrayList는 인덱스를 통한 접근이 가능해서 특정 인덱스의 원소를 조회할 때는 좋지만 추가, 삭제 할 때 비효율적입니다.
    • LinkedList는 원소를 추가하고 삭제할 때는 효율적 이지만 순회할 때는 O(n)이 소요됩니다.
  • Set은 유일한 원소들로 이뤄진 컬렉션입니다. 중복된 원소가 들어갈 수 없습니다.
    • HashSet은 해시 테이블이 기반인 Set입니다. 원소의 해시 코드에 따라 원소 순서가 정해집니다.
    • LinkedHashSet은 해시 테이블이 기반이지만 삽입 순서를 유지하기 때문에 원소를 순서대로 순회할 수 있습니다.
    • TreeSet은 이진 검색 트리(binary search tree)가 기반이며 Comparable에 따라 순서가 정해집니다.

시퀀스

이터러블과 비슷하게 시퀀스도 iterator()를 제공합니다. 하지만 시퀀스는 지연 계산(lazy)이기 때문에 이터러블과는 다르게 동작합니다.

시퀀스의 구현 클래스는 객체가 생성될 때 원소를 초기화 하지 않고 요청에 따라 원소를 계산하기 때문에 원소를 저장하지 않습니다.

맵은 key, value가 쌍으로 이뤄진 집합입니다. 여기서 key는 set처럼 유일하게 존재하고 value는 중복을 허용합니다.

Collection의 하위 타입은 아니지만 원소들을 Collection처럼 사용할 수 있습니다.

구현 클래스는 Set과 비슷하게 HashMap, LinkedHashMap, TreeMap등이 있습니다.

Comparable과 Comparator

Comparable의 인스턴스는 자연적인 순서(natural order)를 지원하며 동일한 타입의 다른 인스턴스와 순서를 비교할 때 사용할 수 있는 compareTo() 함수를 제공합니다. Comparable을 구현하면 자동으로 <, >등의 연산을 사용할 수 있습니다.

compareTo() 함수는 수신객체가 인자로받은 객체보다 크면 양수, 작으면 음수, 같으면 0을 반환합니다.

class Person(
    val firstName: String,
    val familyName: String,
    val age: Int
) : Comparable<Person> {
    val fullName: String
        get() = "$firstName $familyName"

    override fun compareTo(other: Person): Int {
        return fullName.compareTo(other.fullName)
    }
}

val AGE_COMPARATOR = Comparator<Person> { o1, o2 ->
    o1.age.compareTo(o2.age)
}

val AGE_COMPARATOR = compareBy<Person> { it.age }
val REVERSE_AGE_COMPARATOR = compareByDescending<Person> { it.age }

컬렉션 생성하기

코틀린에서는 컬렉션은 클래스의 생성자를 이용해서 객체를 만들 수 있지만 제공되는 여러 함수를 사용해서 만들 수도 있습니다.

  • emptyList()/emptySet(): 불변인 빈 리스트/집합 인스턴스를 생성합니다.
  • listOf()/setOf(): 인자로 제공한 배열(가변인자)에 기반한 불변 리스트/집합 인스턴스를 만듭니다.
  • listOfNotNull(): 널인 값을 걸러내고 남은 원소들로 이뤄진 새 불변 리스트를 만든다.
  • mutableListOf()/mutableSetOf(): 가변 리스트/집합의 디폴트 구현 인스턴스를 만듭니다. 내부적으로 ArrayList와 LinkedHashSet을 사용합니다.
  • arrayListOf(): 새로운 ArrayList를 만듭니다.
  • hashSetOf()/linkedSetOf()/sortedSetOf(): HashSet/LinkedHashSet/TreeSet의 새 인스턴스를 만듭니다.
fun main() {
    val emptyList = emptyList<String>()
    println(emptyList) // []
    emptyList.add("abc") // Unresolved reference: add

    val singletonSet = setOf("abc")
    println(singletonSet) // [abc]
    singletonSet.remove("abc") // Unresolved reference: remove

    val mutableList = mutableListOf("abc")
    println(mutableList)
    mutableList.add("def")
    mutableList[0] = "xzy"
    println(mutableList) // [xyz, def]

    val sortedSet = sortedSetOf(8, 5, 7, 1, 4)
    println(sortedSet) // [1, 4, 5, 7, 8]
    sortedSet.add(2)
    println(sortedSet) // [1, 2, 4, 5, 7, 8]
}
  • emptyMap(): 빈 불변 맵을 만듭니다.
  • mapOf(): 새 불변 맵을 만듭니다.
  • mutableMapOf(): 가변 맵의 디폴트 구현 인스턴스를 만듭니다. 내부적으로 LinkedHashMap을 사용합니다.
  • hashMapOf()/linkedMapOf()/sortedMapOf(): HashMap/LinkedHashMap/TreeMap의 새 인스턴스를 만듭니다.
fun main() {
    val emptyMap = emptyMap<Int, String>()
    println(emptyMap) // {}
    emptyMap[10] = "Ten" // Unresolved reference: add

    val singletonMap = mapOf(10 to "Ten")
    println(singletonMap) // {10=Ten}
    singletonMap.remove("abc") // Unresolved reference: remove

    val mutableMap = mutableMapOf(10 to "Ten")
    println(mutableMap) // {10=Ten}
    mutableMap[20] = "Twenty"
    mutableMap[100] = "Hundred"
    mutableMap.remove(10)
    println(mutableMap) // {20=Twenty, 100=Hundred}

    val sortedMap = sortedMapOf(3 to "three", 1 to "one", 2 to "two")
    println(sortedMap) // {1=one, 2=two, 3=three}
    sortedMap[0] = "zero"
    println(sortedMap) // {0=zero, 1=one, 2=two, 3=three}
}

시퀀스는 sequence { }와 yield(), yieldAll()로 만듭니다.

fun main() {
    val numbers = sequence {
        yield(0)
        yieldAll(listOf(1, 2, 3))
        yieldAll(intArrayOf(4, 5, 6).iterator())
        yieldAll(generateSequence(10) { if (it < 50) it * 3 else null })
    }
    println(numbers.toList()) // [0, 1, 2, 3, 4, 5, 6, 10, 30, 90]
}

컬렉션 사이의 변환을 해주는 함수가 있습니다.

fun main() {
    println(listOf(1, 2, 3, 2, 3).toSet()) // [1, 2, 3]
    println(arrayOf("red", "green", "blue").toSortedSet()) // [blue, green, red]
    println(mapOf(1 to "one", 2 to "two", 3 to "three").toList()) // [(1, one), (2, two), (3, three)]
    println(sequenceOf(1 to "one", 2 to "two", 3 to "three").toMap()) // {1=one, 2=two, 3=three}
}

기본 컬렉션 연산

컬렉션에 iterator()함수가 있으면 for 루프를 이용할 수 있습니다.

다만 map의 경우는 Map.Entry 타입이 리턴 되기 때문에 다르게 사용해야 합니다.

fun main() {
    val list = listOf(1, 2, 3)
    for (item in list) {
        println(item)
    }

    val map = mapOf(1 to "one", 2 to "two", 3 to "three")
    for ((key, value) in map) {
        println("$key -> $value")
    }
}

for 루프 대신 forEach 함수를 사용 할 수 있습니다.

fun main() {
    intArrayOf(1, 2, 3).forEach { println(it) }
    listOf("a", "b", "c").forEach { println(it) }
    sequenceOf("a", "b", "c").forEach { println(it) }
    mapOf(1 to "one", 2 to "two", 3 to "three").forEach { key, value -> println("$key -> $value") }

    intArrayOf(1, 2, 3).forEachIndexed { index, i -> println("$index $i") } // 인덱스 참조가 필요한 경우
}

그 외에 컬렉션 타입은 아래와 같은 함수를 제공합니다.

  • size: 원소의 개수
  • isEmpty, isNotEmpty(): 원소가 없는지 검사
  • contains()/containsAll(): 인자로 지정한 원소가 컬렉션에 존재하는지 검사, equals 메소드가 구현되어 있어야 정상동작
    • contains는 in 예약어로도 사용가능
fun main() {
    val list = listOf(1, 2, 3)
    println(list.isEmpty()) // false
    println(list.size) // 3
    println(list.contains(4)) // false
    println(2 in list) // true
    println(list.containsAll(listOf(1, 2))) // true
}

MutableCollection 타입은 원소를 추가하거나 제거할 수 있는 함수를 제공합니다.

fun main() {
    val list = arrayListOf(1, 2, 3)
    list.add(4) // 원소 하나 추가: [1, 2, 3, 4]
    list.remove(3) // 원소 하나 제거: [1, 2, 4]
    list.addAll(setOf(5, 6)) // 합집합: [1, 2, 4, 5, 6]
    list.removeAll(listOf(1, 2)) // 차집합: [4, 5, 6]
    list.retainAll(listOf(5, 6, 7)) // 교집합: [5, 6]
    list.clear() // 모든 원소 제거: []

    val list2 = mutableListOf(1, 2, 3)
    list2 += 4 // 원소 하나 추가: [1, 2, 3, 4]
    list2 -= 3 // 원소 하나 제거: [1, 2, 4]
    list += setOf(5, 6) // 합집합: [1, 2, 4, 5, 6]
    list -= listOf(1, 2) // 차집합: [4, 5, 6]
}

인덱스에 접근할 수 있는 함수를 제공합니다.

fun main() {
    val list = listOf(1, 4, 6, 2, 4, 1, 7)
    println(list.get(3)) // 2
    println(list[2]) // 6
    println(list[10]) // Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
    println(list.indexOf(4)) // 1
    println(list.lastIndexOf(4)) // 4
    println(list.indexOf(8)) // -1
}

인덱스를 사용해 원소를 변경할 수 있는 함수를 제공합니다.

fun main() {
    val list = mutableListOf(1, 4, 6, 2, 4, 1, 7)
    list.set(3, 0) // [1, 4, 6, 0, 4, 1, 7]
    list[2] = 1 // [1, 4, 1, 0, 4, 1, 7]
    list.removeAt(5) // [1, 4, 1, 0, 4, 7]
    list.add(3, 8) // [1, 4, 1, 8, 0, 4, 7]
}

subList 함수는 시작인덱스(포함), 끝인덱스(미포함)으로 일부분에 대한 리스트를 만들고 이는 원본과 공유됩니다.

fun main() {
    val list = arrayListOf(1, 4, 6, 2, 5, 1, 7)
    val segment = list.subList(2, 5) // View 라고 부름

    list[3] = 0
    println(segment[1]) // 0
    segment[1] = 8
    println(list[3]) // 8
}

Map도 다양한 함수를 제공합니다.

fun main() {
    val map = mapOf(1 to "I", 5 to "V", 10 to "X", 50 to "L")

    println(map.isEmpty()) // false
    println(map.size) // 4
    println(map.get(5)) // V
    println(map[10]) // X
    println(map[100]) // null
    println(map.getOrDefault(100, "?")) // ?
    println(map.getOrElse(100) { "?" }) // ?
    println(map.containsKey(10)) // true
    println(map.containsValue("C")) // false
    println(map.keys) // [1, 5, 10, 50]
    println(map.values) // [I, V, X, L]
    println(map.entries) // [1=I, 5=V, 10=X, 50=L]
}

MutableMap은 변경 연산과 +, - 연산자를 제공합니다. -연산자는 key만 인자로 받습니다.

fun main() {
    val map = sortedMapOf(1 to "I", 5 to "V")

    map.put(100, "C") // {1=I, 5=V, 100=C}
    map[500] = "D" // {1=I, 5=V, 100=C, 500=D}
    map.remove(1) // {5=V, 100=C, 500=D}
    map.putAll(mapOf(10 to "X")) // {5=V, 10=X, 100=C, 500=D}
    map += 50 to "L" // {5=V, 10=X, 50=L, 100=C, 500=D}
    map += mapOf(2 to "II", 3 to "III") // {2=II, 3=III, 5=V, 10=X, 50=L, 100=C, 500=D}
    map -= 100 // {2=II, 3=III, 5=V, 10=X, 50=L, 500=D}
    map -= listOf(2, 3) // {5=V, 10=X, 50=L, 500=D}
}

컬렉션 원소에 접근하기

코틀린 표준 라이브러리에는 개별 컬렉션 원소에 대한 접근을 편하게 해주는 확장 함수들이 있습니다.

first()/last() 함수는 첫 번째와 마지막 원소를 반환하며 컬렉션이 비어있는 경우 NoSuchElementException을 발생 시킵니다. exception을 발생 시키지 않기 위해서는 firstOrNull()/lastOrNull() 함수를 사용하면 됩니다.

fun main() {
    println(listOf(1, 2, 3).first()) // 1
    println(listOf(1, 2, 3).last()) // 3
    println(emptyArray<String>().first()) // Exception in thread "main" java.util.NoSuchElementException: Array is empty.
    println(emptyArray<String>().firstOrNull()) // null

    val seq = generateSequence(1) { if (it > 50) null else it * 3 }
    println(seq.first()) // 1
    println(seq.last()) // 81

    println(listOf(1, 2, 3).first { it > 2 }) // 3
    println(listOf(1, 2, 3).lastOrNull { it < 0 }) // null
    println(intArrayOf(1, 2, 3).first { it > 3 }) // Exception in thread "main" java.util.NoSuchElementException: Array contains no element matching the predicate.
}

single() 함수는 싱글턴 컬렉션의 원소를 반환합니다. 컬렉션이 비어있거나 두개 이상인 경우 single()은 exception을 발생시키고 singleOrNull()은 null을 반환합니다.

fun main() {
    println(listOf(1).single()) // 1
    println(emptyArray<String>().singleOrNull()) // null
    println(setOf(1, 2, 3).singleOrNull()) // null
    println(sequenceOf(1, 2, 3).single()) // Exception in thread "main" java.lang.IllegalArgumentException: Sequence has more than one element.
}

elementAt() 함수를 사용하면 인덱스를 사용해서 컬렉션 원소를 읽을 수 있습니다. 리스트의 get() 함수를 일반화한 함수로 배열, 이터러블, 시퀀스 등에 모두 적용할 수 있습니다.

fun main() {
    println(listOf(1, 2, 3).elementAt(2)) // 3
    println(sortedSetOf(1, 2, 3).elementAtOrNull(-1)) // null
    println(arrayOf("a", "b", "c").elementAtOrElse(1) { "???" }) // b

    val seq = generateSequence(1) { if (it > 50) null else it * 3 }
    println(seq.elementAtOrNull(2)) // 9
    println(seq.elementAtOrElse(100) { Int.MAX_VALUE }) // 2147483647
    println(seq.elementAt(10)) // Exception in thread "main" java.lang.IndexOutOfBoundsException: Sequence doesn't contain element at index 10.
}

컬렉션에 대한 조건 검사

all()함수는 컬렉션의 모든 원소가 조건에 만족하면 true를 반환하고 하나라도 만족하지 않으면 false를 반환 합니다.

none()함수는 all과 반대로 모든 원소가 조건에 만족하지 않으면 true를 반환합니다.

any()함수는 하나라도 조건에 만족하면 true를 반환합니다.

fun main() {
    // all
    println(listOf(1, 2, 3, 4).all { it < 10 }) // true
    println(listOf(1, 2, 3, 4).all { it % 2 == 0 }) // false
    println(
        mapOf(1 to "I", 5 to "V", 10 to "X")
            .all { it.key == 1 || it.key % 5 == 0 }
    ) // true

    val seq = generateSequence(1) { if (it < 50) it * 3 else null }
    println(seq.all { it % 3 == 0 }) // false
    println(seq.all { it == 1 || it % 3 == 0 }) // true

    // none
    println(listOf(1, 2, 3, 4).none { it > 5 }) // true
    println(mapOf(1 to "I", 5 to "V", 10 to "X").none { it.key % 2 == 0 }) // false
    println(seq.none { it >= 100 }) // true

    // any
    println(listOf(1, 2, 3, 4).any { it < 0 }) // false
    println(listOf(1, 2, 3, 4).any { it % 2 == 0 }) // true
    println(mapOf(1 to "I", 5 to "V", 10 to "X").any { it.key == 1 }) // true
    println(seq.any { it % 3 == 0 }) // true
    println(seq.any { it > 100 }) // false
}

빈 컬렉션의 경우 all()과 none() 함수는 true를, any() 함수는 false를 반환합니다. 

fun main() {
    listOf<Int>().all { it == 0 } // true
    listOf<Int>().none { it == 0 } // true
    listOf<Int>().any { it == 0 } // false
}

집계

원소의 합계를 계산하거나 최댓값을 찾는 것처럼 컬렉션 내용으로부터 한 값을 계산하는 경우를 집계(aggregation)라고 부릅니다.

count()는 컬렉션의 원소 개수를 반환합니다.

fun main() {
    println(listOf(1, 2, 3, 4).count()) // 4
    println(mapOf(1 to "I", 5 to "V", 10 to "X").count()) // 3

    val seq = generateSequence(1) { if (it < 50) it * 3 else null }
    println(seq.count()) // 5

    println(listOf(1, 2, 3, 4).count { it < 0 }) // 0
    println(listOf(1, 2, 3, 4).count { it % 2 == 0 }) // 2
    println(mapOf(1 to "I", 5 to "V", 10 to "X").count { it.key == 1 }) // 1

    println(seq.count { it % 3 == 0 }) // 4
    println(seq.count { it > 100 }) // 0
}

sum() 함수는 수로 이뤄진 배열, 이터러블, 시퀀스의 산술 합계를 구합니다.

min(), max() 함수는 최소값, 최대값을 구하는 함수입니다.

fun main() {
    println(listOf(1, 2, 3, 4).sum()) // 10
    println(doubleArrayOf(1.2, 2.3, 3.4).sum()) // 6.9

    println(listOf(1, 2, 3, 4).sumOf { it / 4.0 }) // 2.5
    println(arrayOf("1", "2", "3").sumOf { it.toInt() }) // 6

    println(listOf(1, 2, 3, 4).average()) // 2.5
    println(doubleArrayOf(1.2, 2.3, 3.4).average()) // 2.3000000000000003
    println(emptyList<Int>().average()) // NaN

    println(intArrayOf(5, 8, 1, 4, 2).minOrNull()) // 1
    println(intArrayOf(5, 8, 1, 4, 2).maxOrNull()) // 8
    println(listOf("abc", "w", "xyz", "def", "hij").minOrNull()) // abc
    println(listOf("abc", "w", "xyz", "def", "hij").maxOrNull()) // xyz


    class Person(
        val firstName: String,
        val familyName: String,
        val age: Int,
    ) {
        override fun toString(): String {
            return "$firstName $familyName: $age"
        }
    }

    val persons = listOf(
        Person("Brook", "Watts", 25),
        Person("Silver", "Hudson", 30),
        Person("Dane", "Ortiz", 19),
        Person("Val", "hall", 28),
    )

    println(persons.minByOrNull { it.firstName }) // Brook Watts: 25
    println(persons.maxByOrNull { it.firstName }) // Val hall: 28
    println(persons.minByOrNull { it.familyName }) // Silver Hudson: 30
    println(persons.maxByOrNull { it.familyName }) // Val hall: 28
    println(persons.minByOrNull { it.age }) // Dane Ortiz: 19
    println(persons.maxByOrNull { it.age }) // Silver Hudson: 30
}

joinToString() 함수는 컬렉션 원소를 문자열로 엮는 역할을 합니다.

fun main() {
    println(listOf(1, 2, 3).joinToString()) // 1, 2, 3
    println(listOf(1, 2, 3).joinToString { it.toString(2) }) // 1, 10, 11
    println(listOf(1, 2, 3).joinToString(prefix = "[", postfix = "]")) // [1, 2, 3]
    println(listOf(1, 2, 3).joinToString(separator = "|")) // 1|2|3
    println(listOf(1, 2, 3).joinToString(limit = 2)) // 1, 2, ...
    println(listOf(1, 2, 3).joinToString(limit = 1, separator = " ", truncated = "???")) // 1 ???

    val builder = StringBuilder("joinTo: ")
    val list = listOf(1, 2, 3)
    println(list.joinTo(builder, separator = "|")) // joinTo: 1|2|3
}

fold(), reduce() 함수는 두 값을 조합해서 값을 구하는 함수 입니다.

reduce() 함수는 파라미터가 두개인 함수를 파라미터로 받습니다. 첫 번째 인자는 누적된 값이고, 두 번째 인자는 컬렉션의 현재 값입니다.

파라미터로 전달한 함수의 반환값을 다시 첫 번째 인자로 전달해서 컬렉션을 순회 합니다.

만약 초기값을 첫 번째 인자가 아니라 임의로 지정해주고 싶다면 fold() 함수를 사용합니다.

fun main() {
    println(intArrayOf(1, 2, 3, 4, 5).reduce { acc, i -> acc * i }) // 120
    println(listOf("a", "b", "c", "d").reduce { acc, s -> acc + s }) // abcd
    println(intArrayOf(1, 2, 3, 4, 5).reduceIndexed { index, acc, i -> if (index % 2 == 1) acc * i else acc }) // 8
    println(listOf("a", "b", "c", "d").reduceIndexed { index, acc, s -> if (index % 2 == 1) acc + s else acc }) // abd

    println(intArrayOf(1, 2, 3, 4).fold("") { acc, i -> acc + ('a' + i - 1) }) // abcd
    println(listOf(1, 2, 3, 4).foldIndexed("") { index, acc, i -> if (index % 2 == 1) acc + ('a' + i - 1) else acc }) // bd

    println(arrayOf("a", "b", "c", "d").reduceRight { s, acc -> acc + s }) // dcba
    println(listOf("a", "b", "c", "d").reduceRightIndexed { index, s, acc -> if (index % 2 == 0) acc + s else acc }) // dca
    println(intArrayOf(1, 2, 3, 4).foldRight("") { n, acc -> acc + ('a' + n - 1) }) // dcba
    println(listOf(1, 2, 3, 4).foldRightIndexed("") { index, i, acc ->
        if (index % 2 == 0) acc + ('a' + i - 1) else acc
    }) // ca
}

걸러내기

조건에 만족하지 못하는 원소들은 걸러내 버리고 조건에 만족하는 원소들만 남기는 함수입니다.

fun main() {
    println(listOf("red", "green", "blue", "green").filter { it.length > 3 }) // [green, blue, green]
    println(setOf("red", "green", "blue", "green").filter { it.length > 3 }) // [green, blue]
    println(arrayOf("red", "green", "blue", "green").filter { it.length > 3 }) // [green, blue, green]
    println(byteArrayOf(1, 2, 3, 4, 5).filter { it % 2 == 0 }) // [2, 4]
    println(mapOf("I" to 1, "V" to 5, "X" to 10, "L" to 50).filter { it.value > 5 }) // {X=10, L=50}

    println(listOf("red", "green", "blue").filterNot { it.length > 3 }) // [red]
    println(mapOf("I" to 1, "V" to 5, "X" to 10, "L" to 50).filterNot { it.value > 5 }) // {I=1, V=5}

    val list = listOf("red", "green", "blue", "orange")
    println(list.filterIndexed { index, s -> s.length > 3 && index < list.lastIndex }) // [green, blue]

    val list2 = listOf("red", null, "green", null, "blue")
    println(list2.filterNotNull()) // [red, green, blue]

    val hotchpotch = listOf(1, "two", 3, "four", 5, "six")
    val numbers = hotchpotch.filterIsInstance<Int>()
    val strings = hotchpotch.filterIsInstance<String>()
    println(numbers.filter { it > 2 }) // 3, 5
    println(strings.filter { it != "two" }) // four, six
}

변환

변환에는 mapping, flatten, association가 있습니다.

mapping은 원소들을 다른 것으로 매핑하기 위해 사용합니다.

flatten은 여러 컬렉션을 하나의 컬렉션으로 합쳐주기 위해서 사용합니다.

association은 원본 컬렉션을 Map으로 변환하기 위해서 사용합니다.

fun main() {
    println(setOf("red", "green", "blue").map { it.length }) // [3, 5, 4]
    println(listOf(1, 2, 3, 4).map { it * it }) // [1, 4, 9, 16]
    println(byteArrayOf(10, 20, 30).map { it.toString(16) }) // [a, 14, 1e]

    println(arrayOf("1", "red", "2", "green", "3").mapNotNull { it.toIntOrNull() }) // [1, 2, 3]
    println(listOf("1", "red", "2", "green", "3").mapIndexedNotNull { index, s -> s.toIntOrNull()?.let { index to it } }) // [(0, 1), (2, 2), (4, 3)]

    println(setOf("abc", "def", "ghi").flatMap { it.asIterable() }) // [a, b, c, d, e, f, g, h, i]
    println(Array(3) { it + 1 }.flatMap { 1..it }) // [1, 1, 2, 1, 2, 3]

    println(listOf(listOf(1, 2), setOf(3, 4), listOf(5)).flatten()) // [1, 2, 3, 4, 5]

    println(listOf("red", "green", "blue").associateWith { it.length }) // value selector: {red=3, green=5, blue=4}
    println(listOf("red", "green", "blue").associateBy { it.length }) // key selector: {3=red, 5=green, 4=blue}
    println(listOf("red", "green", "blue").associate { it.uppercase() to it.length }) // transform: {RED=3, GREEN=5, BLUE=4}
    println(listOf("red", "green", "blue").associateBy(
        keySelector = { it.uppercase() },
        valueTransform = { it.length },
    )) // transform: {RED=3, GREEN=5, BLUE=4}
}

 

하위 컬렉션 추출

fun main() {
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    println(list.slice(1..3)) // [2, 3, 4]

    println(list.take(2)) // [1, 2]
    println(list.takeLast(2)) // [6, 7]
    println(list.drop(2)) // [3, 4, 5, 6, 7]
    println(list.dropLast(2)) // [1, 2, 3, 4, 5]

    println(list.takeWhile { it < 3 }) // [1, 2]
    println(list.takeLastWhile { it > 3 }) // [4, 5, 6, 7]
    println(list.dropWhile { it < 3 }) // [3, 4, 5, 6, 7]
    println(list.dropLastWhile { it > 3 }) // [1, 2, 3]

    println(list.chunked(2)) // [[1, 2], [3, 4], [5, 6], [7]]
    println(list.windowed(3)) // [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7]]
    println(list.windowed(3, step = 2)) // [[1, 2, 3], [3, 4, 5], [5, 6, 7]]
    println(list.windowed(3, step = 2, partialWindows = true)) // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7]]

    println(list.zipWithNext()) // [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7)]
    println(list.zipWithNext { a, b -> a * b }) // [2, 6, 12, 20, 30, 42]
}

순서

fun main() {
    println(intArrayOf(5, 8, 1, 4, 2).sorted()) // [1, 2, 4, 5, 8]
    println(intArrayOf(5, 8, 1, 4, 2).sortedDescending()) // [8, 5, 4, 2, 1]
    println(listOf("abc", "w", "xyz", "def", "hij").sorted()) // [abc, def, hij, w, xyz]
    println(listOf("abc", "w", "xyz", "def", "hij").sortedDescending()) // [xyz, w, hij, def, abc]

    println(intArrayOf(5, 8, 1, 4, 2).sortedBy { it }) // [1, 2, 4, 5, 8]
    println(intArrayOf(5, 8, 1, 4, 2).sortedByDescending { it }) // [8, 5, 4, 2, 1]

    println(intArrayOf(5, 8, 1, 4, 2).reversed()) // [2, 4, 1, 8, 5]
    println(listOf(1, 2, 3, 4, 5).shuffled()) // [5, 2, 1, 4, 3]

    println(intArrayOf(5, 8, 1, 4, 2).also { it.sort() }.toList()) // [1, 2, 4, 5, 8]
    println(intArrayOf(5, 8, 1, 4, 2).also { it.sortDescending() }.toList()) // [8, 5, 4, 2, 1]
    println(intArrayOf(5, 8, 1, 4, 2).also { it.reverse() }.toList()) // [2, 4, 1, 8, 5]
    println(mutableListOf(1, 2, 3, 4, 5).also { it.shuffle() }) // [3, 5, 2, 1, 4]
}
728x90
반응형

'개발 > 코틀린' 카테고리의 다른 글

(코틀린) 제네릭스  (0) 2022.11.12
(코틀린) 클래스 계층 이해하기  (0) 2022.11.06
(코틀린) 특별한 클래스 사용하기  (0) 2022.10.23
(코틀린) 함수형 프로그래밍  (0) 2022.10.10
(코틀린) 클래스  (0) 2022.10.03