오늘도 더 나은 코드를 작성하였습니까?

람다로 프로그래밍(컬렉션 함수형 API) 본문

Kotlin in Action/코틀린 기초

람다로 프로그래밍(컬렉션 함수형 API)

hik14 2020. 8. 16. 18:06

- 함수형 프로그래밍 스타일을 사용하면 컬렉션을 다룰 때 매우 편리하다

- 많은 작업에 라이브러리 함수를 활용하고 코드가 간결해진다.

 

필수적인 함수: filter와 map

 

컬렉션을 활용할 때 기반이 되는 함수이며 대부분의 컬렉션 연산을 이 두 함수를 통해 표현 가능하다

 

filter 함수

 

- 컬렉션은 이터레이션 하면서 주어진 람다에 각 원소를 넘겨서 람다가 true를 반환하는 원소만 모은다.

- predicate를 만족하는 새로운 컬렉션을 반환한다.

- 원치 않는 원소를 제거한다 하지만 filter는 원소를 변환할 수는 없다 

- 원소를 변환하려면 map함수를 사용한다.

 

fun main() {

    val people = listOf(Person("kim",15), Person("hong", 18),
            Person("park",12),Person("Bob", 31), Person("Alice",9))

    println(people.filter { it.age >= 15 })

}

map 함수

 

주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새로운 컬렉션을 만든다.

fun main() {
    val people = listOf(Person("kim",15), Person("hong", 18),
            Person("park",12),Person("Bob", 31), Person("Alice",9))

    println(people.map {it.name})
}

 

*두 함수를 쉽게 연쇄시킬 수 있다.

15세 이상의 사람의 이름 출력하기

fun main() {
    val people = listOf(Person("kim",15), Person("hong", 18),
            Person("park",12),Person("Bob", 31), Person("Alice",9))

    println(people.filter { it.age >= 15 }.map { it.name})
}

최고 연장자 구하기

fun main() {
    val people = listOf(Person("kim",15), Person("hong", 18),
            Person("park",12),Person("Bob", 31), Person("Alice",9))

   //println(people.filter { it.age == people.maxBy(Person::age)!!.age})
   // maxBy 함수가 사람이 100명이라면 100번 호출됨 

	
    val maxAge = people.maxBy { it.age }!!.age
    println(people.filter { it.age == maxAge })
}

주석을 처리한 부분처럼 람다 안에 람다식을 넣을 경우 100명의 사람이 있다면 연산이 100번된다. 매우 불합리하다.

 

map 컬렉션의 경우 키와 값을 처리하는 함수가 따로 존재한다.

 

filterKeys MapKeys filterValues mapValues

fun main() {

    val numberMap = mapOf(0 to "zero", 1 to "one", 2 to "two", 3 to "three", 4 to "four")

    println(numberMap.filterKeys { it > 3 })
    println(numberMap.mapKeys { it.key * 2 })

    println(numberMap.filterValues { it == "two" })
    println(numberMap.mapValues { it.value.toUpperCase() })
}

all,  any, count, find 컬렉션 predicate 적용하기


fun main() {

    val people = listOf( Person("Alice", 27), Person("Bob", 31), Person("kim", 19),
    Person("hong", 25), Person("park", 12))

    val canBeInClub25 = {p:Person -> p.age >= 25}

    println(people.all(canBeInClub25)) // 모든사람이 25살 이상을 만족하는지 확인.
    println(people.any(canBeInClub25)) // 25살 이상의 사람이 존재하는지
    println(people.count(canBeInClub25)) // 몇명이나 25살 이상인지 확인한다.
    println(people.find(canBeInClub25)) // 가장 조건을 먼저 만족한다고 확인된 원소를 가져온다. 없으면 null을 반환한다. 
}

* people.filter(canBeInClub25). size,  25살 이상의 사람을 모은 중간 컬렉션이 생기지만

count를 사용하면 그 숫자만 추적하기 때문에 count가 훨씬 효과적이다.

 

 

groupBy : 리스트를 여러 그룹으로 이루어진 맵으로 변경

어떤 특성에 따라 여러 그룹으로 나누고 싶을 때 사용한다.

아래의 예 같은 경우는 Map <Int, List <Person>>을 반환한다.

fun main() {
    val list = listOf("a", "ab", "abc", "bcd","bbb")
    println(list.groupBy { it.first() })
}

 

flatMap과 flatten: 중첩된 컬렉션 안의 원소 처리 하기

 

flatMap

인자로 주어진 람다를 컬렉션의 모든 객체에 적용한다.(맵핑)

- 람다를 적용한 결과 얻어진 리스트를 한 개의 리스트로 모아준다.(펼치기)

 

fun main() {

    val strList = listOf("abcd", "defgh")

    println(strList.flatMap { it.toList() })
}

1. "abcd"  -> [a, b, c, d]   "defgh" ->  [d, e, f, g, h]

2.   [ [a, b, c, d] , [d, e, f, g, h] ] ---> [a, b, c, d, d, e, f, g, h]

 

data class Book(val title: String, val authors: List<String>)

fun main() {
    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 })
    println(books.flatMap { it.authors }.toSet())
}

1 인자로 주어진 람다 적용. 

Book("Thursday Next", listOf("jasper Fforde")) ----> listOf("jasper Fforde")

Book("Mort", listOf("Terry Pratchett")) ----> listOf("Terry Pratchett")

Book("Good Omens", listOf("Terry Pratchett", "Neil Gaiman")) ----> listOf("Terry Pratchett", "Neil Gaiman")

 

2 결과 얻어진 리스트를 한 개의 리스트로 모아준다

[jasper Fforde, Terry Pratchett, Terry Pratchett, Neil Gaiman]

 

3 집합 연산 적용

[jasper Fforde, Terry Pratchett, Neil Gaiman]

 

* 리스트에 리스트가 있는데 중첩된 리스트의 원소를 한 리스트로 모아야 할 경우 flatMap이 효과적이다.

 

list 안에 list가 있는데 각 원소를 한 리스트로 모으고 싶다면 flatMap, flatten을 사용해라.

특별히 변환할 필요가 없다면 flatten을 통해 리스트를 평평하게 펼치기만 한다.

fun main() {
    val listOfList = listOf( listOf("a","b","c"), listOf("c","d","e"), listOf("f","g","h") )
    println(listOfList.flatten())
}

 

** 컬렉션을 다루는 코드를 작성할 경우 원하는 바를 어떻게 일반적인 변환을 통해 표현할지 생각해보고

그 변환에 맞는 라이브러리 함수가 있는지 찾아봐라 대부분 구현되어있다.

그러면 직접 구현하는 것보다 훨씬 빠르고 효과적으로 문제를 해결할 수 있다.