5. [2] Collection 함수형 API

(1) filter와 map

val list= listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 })
// [2, 4]
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.filter { it.age > 30 })
// [Person(name=Bob, age=31)]

[filter]

  • 컬렉션을 이터레이션하면서 주어진 람다에 각 원소를 넘겨서 람다가 true를 반환하는 원소만 모은다.
  • 주어진 술어를 만족하는 모든 원소를 선택한다. (술어란 참/거짓을 반환하는 함수를 말함.)
  • 컬렉션에서 원치 않는 원소를 제거한다. 하지만 변환하는 것은 불가능하다.
val list = listOf(1, 2, 3, 4)
println(list.map { it * it })
// [1, 4, 9, 16]
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name })
people.filter { it.age > 30 }.map(Person::name)
// [Bob]
val numbers = mapOf(0 to "Zero", 1 to "One")
println(numbers.mapValues { it.value.toUpperCase() })
// {0=ZERO,1=ONE}

[map]

  • 주어진 람다를 컬렉션의 각 원소에 적용한 결과를 모아서 새 컬렉션을 만든다.
  • 람드를 컬렉션의 모든 원소에 적용한 결과를 수집한다.
  • 필터와 변환함수를 맵에 적용하는 것도 가능하다.
  • 맵의 경우, 키와 값을 처리하는 함수가 따로 존재하는데, filterKeys, mapKeys는 키를 반환하고, filterValue, mapValue는 값을 반환한다.

(2) all, any, count, find

val canBeInClub27 = {p: Person -> p.age <= 27}
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.all(canBeInClub27))    // false
println(people.any(canBeInClub27))    // true
println(people.count(canBeInClub27))  // 1
println(people.find(canBeInClub27))   // Person(name=Alice, age=27)
val list = listOf(1, 2, 3)
println(!list.all { it == 3 })  // !는 못 볼 경우가 높으므로, any를 사용하는 것이 가독성이 높다.
println(list.any { it! == 3 })  // any를 사용하려면 술어를 부정해야 함.
// true
  • all과 any는 컬렉션에 한해 자주 수행하는 연산으로 컬렉션의 모든 원소가 어떤 조건을 만족하는지 판단한다.
  • count는 조건을 만족하는 원소의 개수를 반환하고, find는 조건을 만족하는 첫 번째 원소를 반환한다.
  • 특정 조건에 대해서 !all과 any는 드 모르강의 법칙에 의해 동일하다.
  • find에서 조건을 만족하는 원소가 하나라도 있는 경우 가장 먼저 조건을 만족한다고 확인한 원소를 반환한다.

(3) 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)]}
val list = listOf("a", "b", "c")
println(list.groupBy(String::first))  // {a=[a, ab], b=[b]}
  • 연산의 결과는 key이고, key 값에 따른 각그룹이 값인 맵이다.
  • 각 그룹은 리스트 이기 때문에 groupBy의 결과 타입은 Map<Int, list<Person>>이다.
  • 다른 예로 멤버 참조를 활용하여 문자열을 첫 글자에 따라 분류하는 코드도 만들 수 있다.
  • first는 확장 함수 이지만, 여전히 멤버 참조를 사용하여 first에 접근할 수 있다.

(4) flatMap과 flatten

class Book(val title: String, val authors: List<String>)
val books = listOf(/* 수 많은 책과 작가들 */)
books.flatMap { it.authors }.toSet()  // Books 컬렉션에 있는 책을 쓴 모든 저자의 집합
// [작가1, 작가2, 작가3...]
val strings = listOf("abc","def")
println(strings.flatMap { it.toList() })  // [a, b, c, d, e, f]
  • flatMap 함수는 먼저 인자로 주어진 람다를 컬렉션의 모든 객체에 적용하고, 람다를 적용한 결과의 리스트를 모은다.
  • toList 함수를 문자열에 적용하면 그 문자열에 속한 모든 문자로 이루어진 리스트가 만들어진다.
  • flatMap 함수는 다음 단계로 리스트의 리스트에 들어있던 모든 원소로 이루어진 단일 리스트를 반환한다.
  • 리스트의 리스트가 있는데 모든 중첩된 리스트의 원소를 한 리스트로 모아야 한다면 flatMap을 사용한다.
  • 반환해야할 내용이 없다면 리스트를 평평하게 펼치면 된다. 그런 경우 flatten을 사용할 수 있다.