Lined Notebook

[자바 개발자를 위한 코틀린 입문] 컬렉션을 함수형으로 다루기

by dev.hardy

코틀린에서 컬렉션을 함수형으로 다루는 방법

인프런에서 최태현 지식 공유자님의
자바 개발자를 위한 코틀린 입문을 수강하며 정리한 내용 입니다.
하단에 강의 링크 있습니다.

필터와 맵

예) 사과만 주세요 : filter

fun main() {
    val fruits = listOf(
        Fruit(1,"사과", 3000, 2000),
        Fruit(2,"사과", 4000, 3000),
        Fruit(3,"바나나", 6000, 5000),
        Fruit(4,"수박", 13000, 12000),
    )

    val apples = fruits.filter { fruit -> fruit.name == "사과"}

        val applesIndex = fruits.filterIndexed{ idx, fruit -> // (1)
        println(idx)
        fruit.name == "사과"
    }
}

(1) filter에서 인덱스가 필요하다면 → filterIndexed 사용하면 된다.

예) 사과의 가격들을 알기: map

fun main() {
    val fruits = listOf(
        Fruit(1,"사과", 3000, 2000),
        Fruit(2,"사과", 4000, 3000),
        Fruit(3,"바나나", 6000, 5000),
        Fruit(4,"수박", 13000, 12000),
    )

    val applePrices = fruits
        .filter { fruit -> fruit.name == "사과" }
        .map { fruit -> fruit.currentPrice }

}

→ 인덱스가 필요하다면: mapIndexed 사용하면 된다.

예) Mapping 결과가 null이 아닌 것만 가져오고 싶다면?

val bananaNotNull = fruits.filter { fruit -> fruit.name == "바나나" }
        .mapNotNull { fruit -> fruit.nullOrValue() } 

→ filter에 null 이 아닌것만 넘어오는 것을 추가하지 않고 mapNotNull 을 사용하면 null이 아닌것만 mapping 할 수 있다.

다양한 컬렉션 처리 기능

all : 조건을 모두 만족하면 true 그렇지 않으면 false

예) 모든 과일이 바나나 인가요?

fun main() {
    val fruits = listOf(
        Fruit(1,"사과", 3000, 2000),
        Fruit(2,"사과", 4000, 3000),
        Fruit(3,"바나나", 6000, 5000),
        Fruit(4,"수박", 13000, 12000),
    )

    val targetName = "바나나"
    val isAllBanana = fruits.all { fruit -> fruit.name == targetName }
    println(isAllBanana) // false
}

→ all과 반대로 none 도 있다. (조건을 모두 불만족하면 true 그렇지 않으면 false)

any : 조건을 하나라도 만족하면 true, 그렇지 않으면 false

예) count: 개수를 세는 것 / 사과의 개수는 총 몇개인지?

fun main() {
    val fruits = listOf(
        Fruit(1,"사과", 3000, 2000),
        Fruit(2,"사과", 4000, 3000),
        Fruit(3,"바나나", 6000, 5000),
        Fruit(4,"수박", 13000, 12000),
    )

    val targetName = "사과"
    val appleCount = fruits.count { fruit -> fruit.name == targetName }
    println(appleCount) // 2
}

그 외의

  • sortedBy : (오름차순) 정렬 / sortedByDescending : (내림차순) 정렬
  • distinctBy : 변형된 값을 기준으로 중복 제거

예) 첫번째 과일만 주세요

  • first : 첫번째 값을 가져온다. (무조건 null이 아니어야 한다.)
  • firstOrNull : 첫 번째 값 또는 null일 경우 null을 가져온다.
fun main() {
    val fruits = listOf(
        Fruit(1,"사과", 3000, 2000),
        Fruit(2,"사과", 4000, 3000),
        Fruit(3,"바나나", 6000, 5000),
        Fruit(4,"수박", 13000, 12000),
    )

    val fruit = fruits.first()
    println("id = ${fruit.id}") // id = 1 
}

반대로 last , lastOrNull 도 있다.

List를 Map으로

예) 과일이름 → List<과일> 형식의 (key, value) 형태인 Map을 만들어보자

fun main() {
    val fruits = listOf(
        Fruit(1,"사과", 3000, 2000),
        Fruit(2,"사과", 4000, 3000),
        Fruit(3,"바나나", 6000, 5000),
        Fruit(4,"수박", 13000, 12000),
    )

    val fruitMap : Map<String, List<Fruit>> = fruits.groupBy { fruit -> fruit.name }
    println(fruitMap["사과"])
}

/**
[Fruit(id=1, name=사과, factoryPrice=3000, currentPrice=2000), Fruit(id=2, name=사과, factoryPrice=4000, currentPrice=3000)]
*/

예) id가 key , 과일이 value 인 Map

fruits.associateBy { fruit -> fruit.id }

→ groupBy와 달리 associateBy 는 value에 단일 객체가 들어간다.

🤠 key와 value를 동시에 처리 가능하다.

예) (과일이름 , List<출고가>) 인 MAP

fruits.groupBy({fruit -> fruit.name}, {fruit -> fruit.factoryPrice})

예) id → 출고가 Map

fruits.associateBy({fruit -> fruit.id}, {fruit -> fruit.factoryPrice})

중첩된 컬렉션 처리

예) 출고가와 현재가가 동일한 과일들을 골라주세요

fun main() {
    val fruits = listOf(
        listOf(
            Fruit(1,"사과", 3000, 2000),
            Fruit(2,"사과", 4000, 3000),
            Fruit(3,"바나나", 6000, 5000),
            Fruit(4,"수박", 13000, 12000)
        ),
        listOf(
            Fruit(5,"사과", 3000, 2000),
            Fruit(6,"사과", 4000, 3000),
            Fruit(7,"바나나", 6000, 5000),
            Fruit(8,"수박", 13000, 12000)
        )
    )

    fruits.flatMap {list ->  // (1)
        list.filter { fruit -> fruit.factoryPrice == fruit.currentPrice }
    }
}

(1) flatMap을 사용하면 List List → 단일 리스트로 바뀌게 된다.

람다안의 람다가 있기 때문에 다음과 같이 리팩토링 가능하다.

data class Fruit(
    val id: Long,
    val name: String,
    val factoryPrice: Long,
    val currentPrice: Long
){
    val isSamePrice : Boolean // 추가
        get() = factoryPrice == currentPrice
}
fun main() {
    val fruits = listOf(
        listOf(
            Fruit(1,"사과", 3000, 2000),
            Fruit(2,"사과", 4000, 3000),
            Fruit(3,"바나나", 6000, 5000),
            Fruit(4,"수박", 13000, 12000)
        ),
        listOf(
            Fruit(5,"사과", 3000, 2000),
            Fruit(6,"사과", 4000, 3000),
            Fruit(7,"바나나", 6000, 5000),
            Fruit(8,"수박", 13000, 12000)
        )
    )

    fruits.flatMap {list -> list.samePriceFilter} // 확장함수를 호출하여 하나의 람다만 사용하는 것 처럼 보이게
}

val List<Fruit>.samePriceFilter: List<Fruit> // 확장함수를 만든다.
    get() = this.filter(Fruit::isSamePrice)
  • 중첩된 컬렉션은 풀려면 → flatten() 사용

Reference:
자바 개발자를 위한 코틀린 입문

블로그의 정보

개발자 하디 : 기록저장소

dev.hardy

활동하기