728x90
필수적인 함수: filter와 map
filter와 map은 컬렉션을 활용할 때 기반이 되는 함수이다. 대부분의 컬렉션 연산을 이 두 함수를 표현할 수 있다.
filter
fun main(args: Array<String>) {
val list = listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 }) //짝수만 남는다
}
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.filter { it.age > 30 }) //30살 이상인 사람만 필요
}
- 컬렉션을 이터레이션 하면서 주어진 람다에 각 원소를 넘겨 람다가 true를 반환하는 원소만 모은다.
- 결과는 입력 컬렉션의 원소 중에서 주어진 술어를 만족하는 원소만으로 이뤄진 새로운 컬렉션이다.
- filter 함수는 컬렉션에서 원치 않는 원소를 제거한다.
- filter는 원소를 변환할 수는 없다.
- 원소를 변환하기 위해서는 map을 사용하자
map
fun main(args: Array<String>) {
val list = listOf(1, 2, 3, 4)
println(list.map { it * it })
}
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name })//이름을 반환
println(people.map(Person::name))
}
- 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새 컬렉션을 만든다.
- 원본 리스트와 원소의 개수는 같지만 각 원소는 주어진 함수에 따라 변환된 새로운 컬렉션이다.
filter와 map을 조합해서 연쇄 동작시킬 수 있다.
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.filter { it.age > 30 }.map(Person::name) //나이가 30 초과인 사람들의 이름
}
나이가 가장 많은 사람들의 출력
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.filter { it.age == people.maxBy(Person::age)!!.age }) //나이의 최댓값인 사람들의 이름 출력
}
- 위 코드는 최댓값을 구하는 작업을 계속 반복한다는 단점이 있다
- 100명이 있다면 100번 최댓값 연산을 하므로 리펙토링이 필요하다
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val maxAge = people.maxBy(Person::age)!!.age
println(people.filter { it.age == maxAge })
}
- 최댓값 계산 연산을 한 번만 계산한다.
- 꼭 필요하지 않은 경우 계산을 반복하지 마라
- 람다를 인자로 받는 함수에 람다를 넘기면 겉으로 볼 때 간단하게 보이지만 내부 로직이 복잡해 엄청나게 불합리한 계산식이 될 수 있다
필터와 변환 함수를 맴에 적용
fun main(args: Array<String>) {
val numbers = mapOf(0 to "Zero", 1 to "One")
println(numbers.mapValues { it.value.toUpperCase() })
}
- 키와 값을 처리하는 함수가 따로 존재한다
- filterKeys와 mapKeys는 키를 걸러내거나 변환하고, filterValues, mapValues는 값을 걸러 내거나 변환한다.
컬렉션에 술어 적용(all, any, count, find)
컬렉션에 대해 자주 수행하는 연산으로 컬렉션의 모든 원소가 어떤 조건을 만족하는지 판단하는 연산이 있다(any, all)
함수의 조건을 만족하는 원소의 개수를 반환하는 함수(count), 조건을 만족하는 첫 번째 원소를 반환하는 함수(find)가 있다
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClub27 = {p:Person -> p.age <= 27}
println(people.all(canBeInClub27))
}
- 모든 원소가 canBeInClub27 술어를 만족하는지 판단하기 위해 all을 사용했다
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClub27 = {p:Person -> p.age <= 27}
println(people.any(canBeInClub27))
}
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(!list.all { it == 3 }) //!를 눈치채지 못하는 경우가 자주 있기 떄문에 any를 사용하는게 낫다
println(list.any { it != 3 }) //any를 사용하려면 술어를 부정해야한다
}
- 술어를 만족하는 원소가 하나라도 있는지 궁금하면 any를 사용한다
- 어떤 조건에 대해 !all을 수행한 결과와 그 조건의 부정에 대해 any를 수행한 결과는 같다 (드모르간 법칙)
- 어떤 조건에 대해 !any를 수행한 결과와 그 조건의 부정에 대해 all을 수행한 결과도 같다
- 가독성을 높이기 위해 any와 all 앞에 !를 붙이지 않는 게 좋다
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 27), Person("Bob", 31))
val canBeInClub27 = { p: Person -> p.age <= 27 }
println(people.count(canBeInClub27))
}
- 술어를 만족하는 원소의 개수를 구하기 위해 count를 사용한다
TIP count와 size
count를 사용하지 않고 컬렉션을 필터링한 결과의 크기를 가지고 오는 경우가 있다.
하지만 이렇게 처리하면 조건을 만족하는 모든 원소가 들어가는 중간 컬렉션이 생긴다. count는 조건을 만족하는 원소의 개수만을 추적하지 조건을 만족하는 원소를 따로 저장하지 않기 때문에 count가 훨씬 효율적이다.
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 27), Person("Bob", 31))
val canBeInClub27 = { p: Person -> p.age <= 27 }
println(people.find(canBeInClub27))
}
- 술어를 만족하는 원소를 하나 찾고 싶으면 find 함수를 사용한다
- 조건을 만족하는 원소가 하나라도 있는 경우 가장 먼저 조건을 만족한다고 확인된 원소를 반환하며 만족하는 원소가 전혀 없는 경우 null을 반환한다
- find와 findOfNull 은 같으며, 더 명확하게 함수를 사용하고 싶다면 findOfNull을 사용하자
리스트를 여러 그룹으로 이뤄진 맵으로 변경(groupBy)
컬렉션의 모든 원소를 어떤 특성에 따라 여러 그룹으로 나누어 보자
특성을 파라미터로 전달하면 컬렉션을 자동으로 구분해주는 함수가 groupBy 함수이다
fun main(args: Array<String>) {
val people = listOf(
Person("Alice", 31),
Person("Bob", 29),
Person("Carol", 31))
println(people.groupBy { it.age })
}
fun main(args: Array<String>) {
val list = listOf("a", "ab", "b")
println(list.groupBy(String::first))
}
- 컬렉션의 원소를 구분하는 특성이 키이고, 키 값에 따른 각 그룹이 값인 맵이다
- 각 그룹은 리스트로, groupBy 결과 타입은 Map<Int, List<Person>> 이다.
- 필요하면 이 맵을 mapKeys나 mapValues 등을 사용해 변경할 수 있다
중첩된 컬렉션 안의 원소 처리(flatMap, flatten)
Book 클래스
class Book(val title: String, val authors: List<String>)
도서관에 있는 책의 저자를 모두 모은 집합을 가져오자
fun main(args: Array<String>) {
val books = listOf(
Book("Thursday Next", listOf("Jasper Fforde")),
Book("Mort", listOf("Terry Pratchett")),
Book("Good Omens", listOf("Terry Pratchett", "Neil Gaiman")))
println(books.flatMap { it.authors }.toSet()) //books 컬렉션에 있는 책을 쓴 모든 저자의 집합
}
fun main(args: Array<String>) {
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() })
}
- flatMap 함수는 인자로 주어진 람다를 컬렉션의 모든 객체에 적용하고, 람다를 적용한 결과 얻어지는 여러 리스트를 한 리스트로 한데 모은다.
- 책을 여러 작가가 함께 쓸 수도 있다.
- flatMap 함수는 모든 책의 작가를 평평한 리스트 하나로 모은다
- toSet은 flatMap결과 리스트에서 중복을 없애고 집합으로 만든다
- 리스트의 리스트를 그냥 평평하게 펼치기만 할 경우 flatten을 사용하면 된다.
728x90
'KotlinInAction > 람다로 프로그래밍' 카테고리의 다른 글
| 수신 객체 지정 람다 (with와 apply) (0) | 2022.08.03 |
|---|---|
| 자바 함수형 인터페이스 활용 (0) | 2022.08.03 |
| 지연 계산 컬렉션 연산 (0) | 2022.08.02 |
| 람다 식과 멤버 참조 (0) | 2022.07.31 |