티스토리 뷰
728x90
컬렉션
- 엘리먼트들로 이뤄진 그룹을 저장하기 위해 설계된 객체
- 컬렉션을 조작하는 모든 연산이 인라인 함수
fun main() {
val l1 = listOf(1, 2, 3)
l1.add(4) // error
val l2 = mutableListOf(1, 2, 3)
l2.add(4) // ok
}
- 자바의 컬렉션과 다르게 불변 컬렉션과 가변 컬렉션을 구분한다. (Mutable)
- 불변 컬렉션은 생성한 다음에 원소를 추가/삭제할 수 없다.
- 이터러블과 시퀀스의 차이
두 컬렉션 모두 원소를 순회하는 기능.
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
'kotlin' 카테고리의 다른 글
[kotlin/코틀린] 어노테이션 (0) | 2023.07.19 |
---|---|
[kotlin/코틀린] 제네릭 (0) | 2023.07.19 |
[kotlin/코틀린] 클래스 계층 (0) | 2023.07.19 |
[kotlin/코틀린] 클래스 종류 (이넘 클래스, 데이터 클래스, 인라인 클래스) (0) | 2023.07.19 |
[kotlin/코틀린] 영역 함수 (0) | 2023.07.19 |