ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Kotlin in Action: 5.2 컬렉션 함수형 API
    Book/Kotlin in Action 2022. 11. 17. 23:56

    1. 필수적인 함수: filter와 map

    filter와 map은 컬렉션을 활용할 때 기반이 되는 함수다. 대부분의 컬렉션 연산을 이 두 함수를 통해 표현할 수 있다. 

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

    결과는 입력 컬렉션의 원소 중에서 주어진 술어(참/거짓을 반환하는 함수를 술어라고 한다)를 만족하는 원소만으로 이뤄진 새로운 컬렉션이다.

    data class Person(val name: String, val age: Int)
    val people = listOf(Person("Alice",29),Person("Bob",31)
    println(people.filter{it.age<30})
    //[Person(name=Bob, age=31)}]

    filter 함수는 컬렉션에서 원치 않는 원소를 제거한다. 하지만 filter는 원소를 변환할 수는 없다.

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

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

    vak list = listOF(1,2,3,4)
    println(list.map{it*it})
    //[1, 4, 9, 16]

    결과는 원본 리스트와 원소의 개수는 같지만, 각 원소는 주어진 함수에 따라 변환된 새로운 컬렉션이다.

     

    사람의 리스트가 아니라 이름의 리스트를 출력하고 싶다면 map으로 사람의 리스트를 이름의 리스트로 변환하면 된다.

     

    val people = listOf(Person("Alice",29),Person("Bob",31))
    println(people.map{it.name})
    //[Alice, Bob]

    위의 예제는 멤버 참조를 사용해서 작성할 수 있다.{it.name} => (Person::name)

    그리고 30살 이상인 사람의 이름을 출력하는 연쇄적인 호출도 가능하다.

    people.filter{it.age>30}.map(Person ::name)
    //[Bob]

    이제 이 목록에서 가장 나이 많은 사람의 이름을 알고 싶다고 하자.

     

    먼저 목록에 있는 사람들의 나이의 최댓값을 구하고 나이가 그 최댓값과 같은 모든 사람을 반환하면 된다.

    people.filter{ it.age == people.maxBy(Person::age)!!.age}

    이 코드의 단점이 무엇일까? 만약에 사람이 100이면 최대값을 100번 구해야한다.

     

    그래서 중복된 계산을 제거하고 효과적인 코드를 만들어 보면 

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

    겉으로 보기에는 처음에 짰던 코드가 더 간결해 보이지만 내부 로직이 더 복잡한 경우가 있따. 그래서 항상 코드의 로직을 파악하고 일어나는 일에 대해서 이해해야 한다.

     

    필터와 변환 함수를 맵(키와 값을 연관시켜주는 데이터 구조)에 적용할 수도 있다. => 무슨 말이지~~??

     

     

    2. all, any, count, find: 컬렉션 술어 적용

    컬렉션에 대해 자주 수행하는 연산으로 컬렉션의 모든 원소가 어떤 조건을 만독하는지 판단하는 연산이 있따. 

    코틀린에서는 all과 any가 이런 연산이다 count 함수는 조건을 만족하는 원소의 개수를 반환하며, find 함수 조건을 만족하는 첫 번째 원소를 반환한다.

     

    어떤 사람의 나이가 27살 이하인지 판단하는 술어 함수 canBeInClub27를 만들자.

     

    val canBeInClub27 = { p: Person -> p.age <= 27 }
    //모든 원소가 이 술어를 만족하는지 궁금하다면 all 함수를 쓴다.
    
    val people = listOf(Person("Alice", 27), Person("Bob", 31))
    println(people.all(canBeInClub27)
    //false
    
    //술어를 만족하는 원소가 하나라도 있는지 궁금하면 any를 쓴다.
    println(people.any(canBeInClub27))
    //true

    어떤 조건에 대해 !all을 수행한 결과와 그 조건의 부정에 대해 any를 수행한 결과는 같다. 또 어떤 조건에 대해 !any를 수행한 결과와 그 조선의 부정에 대해 all을 수행한 결과도 같다.

    가독성을 높이려면 any와 all 앞에 !를 붙이지 않는 편이 낫다.

     

    술어를 만족하는 원소의 개수를 구하려면 count를 사용한다.

    val people = listOf(Person("Alice", 27), Person("Bob",31))
    println(people.count(canBeInClub27))
    //1

     

    술어를 만족하는 원소를 하나 찾고 싶으면 find 함수를 사용한다.

    println(people.find(canBeInClub27))

    조건을 만족하는 원소가 하나라도 있는 경우 가장 먼저 조건을 만족한다고 확인된 원소를 반환하며, 만족하는 원소가 전혀 없는 경우 null을 반환한다. find는 firstOrNull과 같다. 

     

    3. groupBy: 리스트를 여러 그룹으로 이뤄진 맵으로 변경

     

    컬렉션의 모든 원소를 어떤 특성에 따라 여러 그룹으로 나눌 수 있다. 특성을 파라미터로 전달하면 컬렉션을 자동으로 구분하는 groupBy 함수가 있다.

     

    val people = listOf(Person("Alice",31), ... Person("Bob",29), Person("Carol",31))
    
    println(people.groupBy{ it.age })

     

    이 연산의 결과는 컬렉션의 원소를 구분하는 특성이 키이고, 키 값에 따른 각 그룹이 값인 맵이다.

     

    이 예제의 출력은 {29 = [Person(name = Bob, age =29)], 31 = [Person (name = Alice, age = 31), Person(name = Carol, age = 31)]} 이다. 

     

    각 그룹은 리스트이고, groupBy의 결과 타입은 Map<Int, List<Person>>이다.

     

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

    flatMap함수는 먼저 인자로 주어진 람다를 컬렉션의 모든 객체에 적용하고(또는 매핑하기) 람다를 적용한 결과 얻어지는 여러 리스트를 한 리스트로 한데 모은다.(또는 펼치기(flatten)) 

     

    Book으로 표현한 책에 대한 정보를 저장하는 도서관이 있다고 가정하자. 책마다 저자가 한 명 또는 여러 명이 있어서 저자를 저장하는 자료구조는 List이다. 

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

     

    도서관에 있는 책의 저자를 모두 모은 집합을 다음과 같이 가져올 수 있다.

    val books = listof(Book("Thusday Next", listOf("Jasper Fforde")),
    		   Book("Mort", listOf("terry Pratchett")),
                       Book("Good Omens", listOf("Terry Pratchett",
                       						     "neil Gaiman"))
                                                 
    println(books.flatMap{it.authors}.toSet()}
    //결과 [Jasper Fforde, Terry Pratchett, Neil Gaiman]

    book.authors 프로퍼티는 작가를 모아둔 컬렉션이다. flatMap 함수는 모든 책의 작가를 평평한(문자열ㄹ만으로 이뤄진 리스트 하나로 모은다. toSet은 중복을 없애고 결과를 집합으로 만든다. 

     

    그렇다면 정확히 flatMap은 언제 사용할까? 

     

    리스트의 리스트가 있는데, 모든 중첩된 리스트의 원소를 한 리스트로 모아야 한다면, flatMap을 떠올릴 수 있을 것이다.

     

    하지만 특별히 변환해야 할 내용이 없다면 리스트의 리스트를 평평하게 펼치기(모으기)만 하면 된다. listOfLists.flatten()처럼 flatten 함수를 사용하면 된다.

     

Designed by Tistory.