티스토리 뷰

kotlin

[kotlin/코틀린] 컬렉션

hrniin 2023. 7. 19. 23:27
728x90

 

컬렉션

  • 엘리먼트들로 이뤄진 그룹을 저장하기 위해 설계된 객체
  • 컬렉션을 조작하는 모든 연산이 인라인 함수

 

fun main() {
    val l1 = listOf(1, 2, 3)
    l1.add(4) // error
     
    val l2 = mutableListOf(1, 2, 3)
    l2.add(4) // ok
}
 
  • 자바의 컬렉션과 다르게 불변 컬렉션과 가변 컬렉션을 구분한다. (Mutable)
  • 불변 컬렉션은 생성한 다음에 원소를 추가/삭제할 수 없다.

 

 

 

  1. 이터러블과 시퀀스의 차이

두 컬렉션 모두 원소를 순회하는 기능.

 

fun main() {
    listOf(1,2,3)
        .filter { print("F$it "); it % 2 == 1 }
        .map { print("M$it "); it * 2}
        .forEach { print("E$it ") }
    	// F1 F2 F3 M1 M3 E2 E6

     sequenceOf(1,2,3)
        .filter { print("F$it "); it % 2 == 1  }
        .map { print("M$it "); it * 2 }
        .forEach { print("E$it ")}
    	// F1 M1 E2 F2 F3 M3 E6 
}
  • 이터러블은 원소 전체를 대상으로 연산을 차근차근 적용해 나간다. (eager order)
  • 시퀀스는 원소 하나하나에 지정한 연산을 한꺼번에 적용한다. (lazy order)

 

fun main() {
    (1..10)
        .filter { print("F$it "); it % 2 == 1 }
        .map { print("M$it "); it * 2 }
        .first()
        // F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 M1 M3 M5 M7 M9

     (1..10).asSequence()
        .filter { print("F$it "); it % 2 == 1 }
        .map { print("M$it "); it * 2 }
        .first()
        // F1 M1
}
  • 이터러블은 중간연산이라는 개념이 없고, 시퀀스는 중간연산이라는 개념이 있다.
  • 중간 처리 단계를 모든 요소에 적용할 필요가 없는 경우에 시퀀스를 사용하여 연산량을 줄일 수 있다.

 

fun main() {
    val i = (1..10)
        .filter { it % 2 == 1 } // List 생성
        .map { it * 2 }         // List 생성

     val s = (1..10).asSequence()
        .filter { it % 2 == 1 } // List 생성 x
        .map { it * 2 }         // List 생성 x
        .toList() 
}
  • 이터러블은 각 단계마다 컬렉션(List)를 생성하지만 시퀀스를 컬렉션을 만들어내지 않기 때문에 최종 연산에 toList() 등을 호출해야 한다.

 

 

 

      2. 컬렉션 생성

  • 생성자 호출하여 생성
  • emptyXXX(): 불변인 빈 컬렉션 생성
  • XXXOf(): 인자로 제공한 원소에 기반하여 불변 컬렉션 생성
  • mutableXXXOf(): 가변 컬렉션 생성 
  • XXX → List, Set, Map ...

 

 

 

      3. 시퀀스 생성

sequenceOf(1, 2, 3)
listOf(1, 2, 3).asSequence()
  • sequenceOf(), asSequence()

 

generateSequence{ readLine()?.toIntOrNull() }
generateSequence(10) { if (it >= 2) it - 2 else null }
 
  • generateSequence() 
    • 널을 반환할 때까지 시퀀스의 원소 생성이 계속된다.

 

sequence {
    yield(0)
    yieldAll(listOf(1, 2, 3))
}
  • sequence()
    • 확장 람다 본문 안에서 시퀀스 뒤에 값을 추가하는 yield(), yieldAll() 함수를 사용한다.
    • suspendable computation

 

 

 

      4. 컬렉션 연산

  • + - 를 통해 원소를 더하거나 삭제할 수 있다. 원본과 다른 새로운 컬렉션을 생성하기 때문에 불변/가변 컬렉션 모두 사용할 수 있다.
    • 가변 컬렉션에서는 +=, -= 복합 연산을 사용할 수 있다. 
  • 리스트도 배열처럼 인덱스로 접근/변경할 수 있다.
    • ex)
    • println(list[2])
    • list[0] = 1
  • subList()를 통해 리스트 일부분을 가져올 수 있다. (얕은 복사)

 

 

 

     5. 원소 접근

fun main() {
    println(emptyArray<String>().first())         // exception
    println(emptyArray<String>().firstOrNull())   // null
     
    println(listOf(1,2,3).lastOrNull { it > 3 }) // null
}
 
  • first() / last(): 첫 번째/마지막 원소 반환
  • firstOrNull()/lastOrNull(): 원소가 없다면 널 반환
  • 조건에 맞는 첫 번째/마지막 원소를 찾기 위해 함수를 사용할 수 있다.

 

  • single(): 원소가 하나인 컬렉션의 원소를 반환한다. 
  • singleOrNull(): 컬렉션이 비어있거나 원소가 두 개 이상이면 널 반환
  • elementAt(): 인덱스를 사용해 컬렉션 원소를 가져온다. get()과 다르게 배열, 이터러블, 시퀀스 등에 모두 적용 가능
    • elementAtOfNull(), elementAtOrElse()

 

 

 

      6. 조건 검사

fun main() {
    val list = listOf(1, 2, 3, 4)
    println(list.all { it < 10 })       // true
    println(list.none { it % 2 == 0 })  // false
    println(list.any { it % 2 == 0 })   // true
}
  • all(): 컬렉션의 모든 원소가 주어진 함수의 조건을 만족하면 true 반환
  • none(): all()과 반대. 조건을 만족하는 원소가 하나도 없으면 true 반환
  • any(): 적어도 하나의 원소가 조건을 만족하면 true 반환
  • 컬렉션이 비어있는 경우 all(), none()은 true, any()는 false 반환

 

 

 

      7. 집계

fun main() {
    println(listOf(1, 2, 3).sum()) // 6
    println(listOf("1","2","3").sum()) // error
    println(listOf("1","2","3").sumOf { it.toInt() }) // 6
}
  • count(): 원소 개수 반환. 원소 개수가 Int.MAX_VALUE보다 크면 예외 (무한 시퀀스)
  • sum(): 수로 이뤄진 컬렉션의 합계 반환
  • sumOf(): 람다로 생성된 원소의 합계 반환
  • average(): 수로 이뤄진 컬렉션의 평균 반환. 컬렉션이 비어있는 경우 Double.NaN을 반환 

 

class C(val i: Int, val s: String)

fun main() {
    val seq = sequenceOf(
        C(20, "a"), // 1
        C(5, "b"),  // 2
        C(10, "c")  // 3
    )
     
    println(seq.minByOrNull { it.i }) // 2
    println(seq.maxByOrNull { it.s }) // 3
     
    println(seq.minWithOrNull(Comparator<C> { c1, c2 -> c1.i.compareTo(c2.i) })) // 2
}
  • minOrNull(), maxOrNull(): 비교 가능한 타입의 값이 들어있는 컬렉션의 최소/최댓값 반환. 컬렉션이 비어있다면 null
  • minByOrNull(), maxByOrNull(): 비교 가능한 값으로 원소를 변환하는 함수를 제공하여 비교할 수 없는 원소들의 컬렉션의 최소/최댓값 반환
  • minWithOrNull(), maxWithOrNull(): 원소를 변환하는 함수가 아닌 Comparator를 인자로 받음

 

fun main() {
    val list = listOf(1, 2, 3)
    println(list.joinToString()) // 1, 2, 3
    println(list.joinToString { it.toString(2) }) // 1, 10, 11
     
    println(list.joinTo(StringBuilder("joinTo: "), separator = "|")) // joinTo: 1|2|3
}
 
  • joinToString(): 컬렉션 원소를 문자열로 만듦.
  • joinTo(): 인자로 받은 StringBuilder 뒤에 원소를 붙임.
    • separator: 원소 사이에 들어갈 구분 문자열 (", ")
    • prefix, postfix: 결과 문자열 맨 앞/뒤에 들어갈 문자열 ("")
    • limit: 최대로 보여줄 수 있는 원소의 개수 (-1: 개수 제한 x)
    • truncated: 원소를 모두 표현하지 못할 때 뒤에 추가하는 문자열 ("...")

 

fun main() {
    val list = (1..5).toList()

    println(list.reduce { acc, n -> acc + n * 2 }) // 29
    println(list.fold(0) { acc, n -> acc + n * 2}) // 30
}
 
  • fold(): 함수를 통해 누적한 값을 반환. 초깃값으로 컬렉션의 첫 번째 원소
  • reduce(): 함수를 통해 누적한 값을 반환. 초깃값을 원하는 대로 지정
  • reduceIndexed(), foldIndexed(): 인덱스를 사용해야 하는 경우
  • reduceRight(), foldRight(): 마지막 원소부터 반대 방향으로 계산 수행

 

 

 

      8. 필터

fun main() {
    val list = listOf("red", "green", "blue")
    println(list.filter { it.length > 3 })	// [green, blue]
    println(list.filterNot { it.length > 3 })	// [red]
}
 
  • filter(): 조건을 만족하는 원소만을 갖는 새로운 불변 컬렉션 생성
    • Array. Iterable, Set에 적용하면 List를 반환
    • map에서 키나 값에만 적용하고 싶다면 filterKeys(), filterValues() 사용
  • filterNot(): filter()와 반대. 조건을 만족하지 않는 원소만 남김.
  • filterIndexed(): 인덱스를 사용해서 걸러내야 하는 경우

 

fun main() {
    val list = listOf("red", "green", "blue", null)
    list.forEach { println(it.length) }                 // error
    list.filterNotNull().forEach { println(it.length) } // ok
}
  • filterNotNull(): 널인 원소를 걸러냄. 항상 non-nullable 타입인 컬렉션 생성.

 

fun main() {
    val list = listOf(1, "two", 3, "four")
    val num = list.filterIsInstance<Int>()
    val str = list.filterIsInstance<String>()
}
  • filterIsInstance(): 특정 타입의 원소만 남김

 

fun main() {
    val list = ArrayList<String>()
    listOf("red", "green", "blue").filterTo(list) { it.length > 3 }
     
    println(list) // [green, blue]
}
  • filterTo(): 새로운 불변 컬렉션을 생성하지 않고, 필터 결과를 인자의 리스트에 넣음.

 

 

 

      9. 변환

fun main() {
    println(listOf("red", "green", "blue").map { it.length })                       // [3, 5, 4]
    println(listOf("1", "red", "2", "green", "3").mapNotNull { it.toIntOrNull() })  // [1, 2, 3]
}
 
  • map(): 컬렉션의 원소에 대해 인자의 함수를 적용하여 새로운 컬렉션을 생성
    • 시퀀스에 적용한 경우는 시퀀스, 나머지는 리스트를 반환
    • 맵에 적용한 후 맵을 반환하고 싶다면 mapKeys(), mapValues()를 사용
  • mapIndexed(): 인덱스를 고려해야 할 경우 사용
  • mapNotNull(), mapIndexedNotNull(): 널이 아닌 값만 새로운 컬렉션에 포함

 

fun main() {
    println(setOf("abc", "def", "ghi").flatMap { it.asIterable() })     // [a, b, c, d, e, f, g, h, i]
    println(listOf(listOf(1, 2), listOf(3, 4), listOf(5)).flatten())    // [1, 2, 3, 4, 5]
}
  • flatMap(): 컬렉션의 각 원소를 컬렉션으로 변환한 다음, 차례로 이어 붙여 하나의 컬렉션으로 합쳐줌.
    • 시퀀스에 적용한 경우는 시퀀스, 나머지는 리스트를 반환
  • flatten(): 원소가 컬렉션인 모든 컬렉션에 적용 가능. 각각의 컬렉션을 이어 붙인 하나의 컬렉션을 반환
  • flatMapTo(): 결과 원소를 인자로 들어온 컬렉션에 추가

 

fun main() {
    println(listOf("red", "green", "blue").associateWith { it.length })                 // {red=3, green=5, blue=4}
    println(listOf("red", "green", "blue").associateBy { it.length })                   // {3=red, 5=green, 4=blue}
    println(listOf("red", "green", "blue").associate { it.uppercase() to it.length })   // {RED=3, GREEN=5, BLUE=4}
}
 
  • associateWith(): 인자의 변환 함수를 바탕으로 맵을 생성. 컬렉션 원소가 키, 함수의 결과가 값.
  • associateBy(): 컬렉션 원소가 값, 변환 함수 결과가 키.
  • associate(): 인자의 변환 함수를 통해 키와 값 모두를 만들어냄.

 

 

 

      10. 하위 컬렉션 추출

fun main() {
    val list = List(6) { it * it } 
    println(list.slice(1..3))           // [1, 4, 9]
    println(list.take(2))               // [0, 1]
    println(list.drop(2))               // [4, 9, 16, 25]
     
    println(list.takeWhile { it < 10 })  // [0, 1, 4, 9]
    println(list.dropWhile { it < 10 })  // [16, 25]
}
 
  • slice() / sliceArray(): 정수 범위를 통해 리스트나 배열의 일부를 추출
  • take() / takeLast(): 원소를 주어진 개수만큼 추출
  • drop() / dropLast(): 주어진 개수만큼 원소를 제거한 나머지를 추출
  • takeWhile() / dropWhile(): 주어진 조건을 만족하지 못하는 원소를 발견할 때까지 원소를 남기거나 제거

 

fun main() {
    val list = List(10) { it * it }
     
    println(list.chunked(3))                // [[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]
    println(list.chunked(3) { it.sum() })   // [5, 50, 149, 81]
     
    println(list.windowed(3))               // [[0, 1, 4], [1, 4, 9], [4, 9, 16], [9, 16, 25], [16, 25, 36], ...]
    println(list.zipWithNext())             // [(0, 1), (1, 4), (4, 9), (9, 16), (16, 25), (25, 36), (36, 49), (49, 64), (64, 81)]
}
  • chunked(): 주어진 개수를 넘지 않는 리스트(chunk)들로 나눔. 각각의 chunk를 변환하는 함수를 지정할 수 있다.
  • windowed(): 일정한 간격으로 슬라이딩 윈도우를 생성
    • step: 인접한 윈도우 사이의 거리 (1)
    • partialWindows: 지정한 윈도우 크기보다 작은 크기의 윈도우를 포함시킬지 여부 (false)
  • zipWithNext(): 원소가 두 개인 윈도우를 생성. Pair의 시퀀스나 리스트를 만들어냄.

 

 

      11. 정렬

class C(val i: Int, val s: String)

fun main() {
    val list = listOf(1, 3, 8, 5, 2)
    println(list.sorted())           // [1, 2, 3, 5, 8]
    println(list.sortedDescending()) // [8, 5, 3, 2, 1]
     
    val seq = sequenceOf(
        C(20, "a"), // 1
        C(5, "b"),  // 2
        C(10, "c")  // 3
    )
     
    println(seq.sortedBy { it.i }.toList()) // 2 -> 3 -> 1
    println(seq.sortedWith(Comparator<C> { c1, c2 -> c1.s.compareTo(c2.s)}).toList()) // 1 -> 2 -> 3
}
  • sorted(): 오름차순으로 원소 정렬
  • sortedDescending(): 내림차순으로 원소 정렬
  • sortedBy(): 원소를 비교 가능한 타입으로 변환하는 함수를 인자로 받음
  • sortedWith(): Comparator 인스턴스를 인자로 받음
  • sort() / sortDescending(): 배열이나 가변 리스트 원본을 정렬

 

  • reversed(): 원소를 역순으로 뒤집음
  • reverse(): 새 컬렉션을 만들지 않고 배열이나 가변 리스트 원본을 뒤집음
  • asReversed(): 원소를 역순으로 뒤집고 새로운 리스트를 반환하지만, 원본에 대한 얕은 복사.
  • shuffled(): 원본 원소를 임의의 순서로 재배치

 

 

728x90
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함