PS 문제를 하나씩 풀다 보니, 공부가 필요하다고 느낀 문법을 정리한 글입니다.
지난 포스트에서
Collection은 여러 데이터를 편하게 다루기 위해 제공되는 라이브러리라고 정리했습니다.
이번 글에서는Collection에 들어 있는 데이터를 더 쉽게 가공하고 처리할 때 자주 사용하는 함수들을 알아보겠습니다.
컬렉션을 다루다 보면 정렬, 변환, 반복, 필터링, 그룹화 같은 작업이 자주 필요합니다.
Kotlin은 이런 작업을 간결하게 처리할 수 있도록 다양한 함수를 제공합니다.
컬렉션에 데이터가 들어 있다면, 원하는 기준에 따라 정렬하고 싶을 때가 많습니다.
이럴 때 사용하는 함수가sorted()와sortedDescending()입니다.
sorted()는 오름차순으로 정렬된 새로운 컬렉션을 반환합니다.sortedDescending()은 내림차순으로 정렬된 새로운 컬렉션을 반환합니다.fun main() {
val a = listOf(2, 3, 1)
println(a.sorted())
println(a.sortedDescending())
val numbersMap = mapOf("key1" to 2, "key2" to 3, "key3" to 1)
println(numbersMap.toSortedMap())
}
[1, 2, 3]
[3, 2, 1]
{key1=2, key2=3, key3=1}
위 예제에서
sorted()는 리스트를 오름차순으로 정렬한 결과를 반환하고,
sortedDescending()은 내림차순 결과를 반환합니다.
또한 Map은 toSortedMap()을 사용하면 키를 기준으로 정렬된 Map을 얻을 수 있습니다.
예제에서는 키가 String이므로 문자열 기준 오름차순으로 정렬됩니다.
map()은 컬렉션의 각 요소를 원하는 형태로 변환할 때 사용하는 함수입니다.
기존 컬렉션을 바꾸는 것이 아니라, 변환된 결과를 담은 새로운 컬렉션을 반환합니다.
fun main() {
val a: List<Int> = listOf(1, 2, 3)
val b = a.map { it * 10 }
println(b)
println(a.map { it * 10 })
}
[10, 20, 30]
[10, 20, 30]
위 예제에서는
a의 각 요소에10을 곱한 새로운 리스트를 만들고 있습니다.
람다식 안의it은 컬렉션의 각 요소를 의미하며, 이를 이용해 원하는 방식으로 값을 변환할 수 있습니다.
forEach()는 컬렉션의 요소를 하나씩 순회하면서 처리할 때 사용합니다.
forEachIndexed()는 여기에 인덱스 정보까지 함께 사용할 수 있게 해줍니다.
fun main() {
val a: List<String> = listOf("abc", "def", "hij")
a.forEach { println(it) }
a.forEachIndexed { index, value ->
println("$index : $value")
}
}
abc
def
hij
0 : abc
1 : def
2 : hij
forEach()는 값만 하나씩 처리하고,
forEachIndexed()는 값과 함께 인덱스도 사용할 수 있다는 차이가 있습니다.
인덱스는 리스트와 마찬가지로0부터 시작합니다.
filter()는 특정 조건을 만족하는 요소만 골라서 새로운 컬렉션으로 반환하는 함수입니다.
예를 들어 정수 리스트에서 짝수만 추려내고 싶다면 다음과 같이 사용할 수 있습니다.
fun main() {
val a: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val b = a.filter { it % 2 == 0 }
println(b)
}
[2, 4, 6]
위 코드에서는 각 요소를 2로 나누었을 때 나머지가 0인 값만 남기고 있습니다.
즉,filter()는 조건에 맞는 데이터만 걸러내는 데 유용합니다.
filterValues()는Map에서 value를 기준으로 필터링할 때 사용하는 함수입니다.
조건을 만족하는 value를 가진 key-value 쌍만 포함하는 새로운Map을 반환합니다.
fun main() {
val map = mapOf("a" to 1, "b" to 2, "c" to 1, "d" to 3)
val filteredMap = map.filterValues { it > 1 }
println(filteredMap)
}
{b=2, d=3}
위 예제에서는 value가 1보다 큰 데이터만 남겼기 때문에
b=2,d=3만 포함된 새로운Map이 반환됩니다.
find()는 조건에 맞는 요소 중 가장 먼저 발견된 값 하나를 반환합니다.
조건에 맞는 값이 없다면null을 반환합니다.
반대로 findLast()는 조건에 맞는 요소 중 가장 마지막 값을 반환합니다.
fun main() {
val a: List<Int> = listOf(1, 2, 3, 4, 5, 6)
val b = a.find { it % 2 == 0 }
val c = a.findLast { it % 2 == 0 }
println("find : $b, findLast : $c")
}
find : 2, findLast : 6
위 코드에서는 짝수 조건을 만족하는 값 중
가장 먼저 찾은 값은2, 가장 마지막 값은6입니다.
이와 비슷한 함수로는 first(), last(), firstOrNull(), lastOrNull() 등이 있습니다.
이 함수들은 컬렉션이 특정 조건을 만족하는지 검사할 때 자주 사용됩니다.
any() : 조건을 만족하는 요소가 하나라도 있으면 trueall() : 모든 요소가 조건을 만족해야 truenone() : 조건을 만족하는 요소가 하나도 없으면 truefun main() {
val a: List<Int> = listOf(1, 2, 3, 4, 5, 6)
if (a.any { it % 2 == 0 }) {
println("짝수 데이터가 존재합니다.")
}
if (a.all { it % 2 == 0 }) {
println("모든 데이터가 짝수입니다!")
} else {
println("홀수 데이터도 존재합니다.")
}
if (a.none { it > 7 }) {
println("7보다 큰 데이터는 없습니다.")
}
}
짝수 데이터가 존재합니다.
홀수 데이터도 존재합니다.
7보다 큰 데이터는 없습니다.
any()는 “하나라도 있는가”,
all()은 “전부 만족하는가”,
none()은 “아예 없는가”를 확인하는 함수라고 생각하시면 이해하기 쉽습니다.
flatMap()은 각 요소를 컬렉션으로 변환한 뒤,
그 결과를 하나의 컬렉션으로 평탄화해서 반환하는 함수입니다.
fun main() {
val a: List<Int> = listOf(1, 2, 3)
val flatA = a.flatMap { listOf(it * 3) }
println(flatA)
val flatList = a.flatMap { listOf(it * 3, it * 4) }
println(flatList)
}
[3, 6, 9]
[3, 4, 6, 8, 9, 12]
첫 번째 예제는 각 요소를 하나의 값으로 변환했기 때문에 일반적인
map()과 비슷해 보입니다.
하지만 두 번째 예제처럼 각 요소를 여러 값으로 바꾸면, 그 결과를 한 번에 펼쳐서 하나의 리스트로 만들어줍니다.
즉, flatMap()은 변환과 평탄화를 동시에 수행하는 함수입니다.
getOrElse()는 지정한 인덱스의 값이 존재하면 그 값을 가져오고,
존재하지 않으면 기본값을 대신 반환하는 함수입니다.
fun main() {
val a: List<Int> = listOf(1, 2, 3, 4, 5, 6)
println(a.getOrElse(2) { 10 })
println(a.getOrElse(10) {
println("10번째 인덱스는 없습니다.")
-1
})
}
3
10번째 인덱스는 없습니다.
-1
첫 번째 결과는 인덱스
2가 실제로 존재하므로 해당 값인3이 반환됩니다.
반면 두 번째 결과는 인덱스10이 없기 때문에, 람다식 안의 코드가 실행되고 기본값-1이 반환됩니다.
즉, getOrElse()는 없는 인덱스에 접근할 때 예외 대신 대체 값을 사용하고 싶을 때 유용합니다.
groupBy()는 특정 기준에 따라 요소를 묶어서Map형태로 반환하는 함수입니다.
같은 기준을 가진 요소끼리 하나의 그룹으로 묶을 수 있습니다.
fun main() {
val arr: List<String> = listOf("apple", "banana", "cherry", "date", "elderberry")
val arrLength = arr.groupBy { it.length }
println(arrLength)
}
{5=[apple], 6=[banana, cherry], 4=[date], 10=[elderberry]}
위 예제에서는 문자열의 길이를 기준으로 그룹화하였습니다.
그래서 길이가6인"banana"와"cherry"가 같은 그룹으로 묶여 있는 것을 확인할 수 있습니다.
groupBy()는 “같은 조건을 가진 데이터끼리 묶고 싶다”는 상황에서 매우 유용하게 사용할 수 있습니다.
이번 글에서는 Kotlin Collection에서 자주 사용하는 함수들을 정리해보았습니다.
컬렉션은 단순히 값을 저장하는 것에서 끝나지 않고,
정렬하고, 변환하고, 걸러내고, 검사하고, 그룹화하는 과정까지 자주 함께 사용됩니다.
이번에 정리한 함수들을 다시 보면 다음과 같습니다.
sorted(), sortedDescending()map(), flatMap()forEach(), forEachIndexed()filter(), filterValues()find(), findLast()any(), all(), none()getOrElse()groupBy()문제를 풀거나 실전 코드를 작성할 때 이런 함수들을 익혀두면
반복문을 길게 작성하지 않고도 훨씬 간결하게 데이터를 처리할 수 있습니다.
다음에는 Collection에서 자주 쓰이는 다른 함수들도 이어서 정리해보려고 합니다.
Collections overviewCollection operations overview