코틀린 (Kotlin) 컬렉션

코틀린 컬렉션

fun functionalFilter(numList : List<Int>) : List<Int> = numList.filter { it % 2 == 0 }
  • 코드 간결성/가독성 향상
  • 결과값을 저장하기 위한 별도의 리스트를 생성할 필요가 없다
  • 비즈니스 로직에 집중
  • 버그가 발생할 확률이 적다
  • 테스트가 용이
  • 유지보수가 용이

fun functionalMap(numList: List<Int>) : List<Int> {
    return numList.map { it + 2 }
}
  • map 함수는 내부적으로 새로운 리스트를 만들어서 반환 -> 부수효과 없음

tailrec fun<T, R> FunList<T>.map(acc: FunList<R> = FunList.Nil, f: (T) -> R): FunList<R> = when (this) {
    FunList.Nil -> acc.reverse()
    is FunList.Cons -> tail.map(acc.addHead(f(head)), f)
}
  • 제네릭과 고차함수를 활용해서 일반화된 함수를 만들고, 재사용성을 높이는 것이 함수적 접근 방식

명령형과 함수형의 차이

fun func(intList: List<Int>) =
    intList
        .map { n ->
            println("map=$n")
            n*n }
        .filter { n ->
            println("filter=$n")
            n < 10 }
        .first()

println(func(listOf(1,2,3,4,5))) //"1"출력


  • map 5번 실행, filter 5번 실행, first() 를 실행하기때문에 명령형보다 반복 계산이 많을 수 있음
코틀린에서 아래 상황에서는 컬렉션을 사용하지 않는것이 좋다
  • 성능에 민감할때
  • 컬렉션의 크기가 고정되어 있지 않을 때
  • 고정된 컬렉션 크기가 매우 클 때
코틀린의 컬렉션은 기본적으로 값이 즉시 평가되기 때문에 게으른 평가로 실행되지 않아 성능이 떨어진다.

시퀀스


fun seqFunc(intList: List<Int>) =
    intList
        .asSequence()
        .map { n ->
            println("map=$n")
            n*n }
        .filter { n ->
            println("filter=$n")
            n < 10 }
        .first()

시퀀스를 사용해서 성능을 향상 시킬수 있다.


Lazy 컬렉션

  • 람다로 매개변수를 받으면 Cons 생성되는 시점에 매개변수가 평가되지 않는다.

sealed class FunStream<out T> {
    object Nil : FunStream<Nothing>()
    data class Cons<out T>(val head: () -> Tval tail: () -> FunStream<T>) : FunStream<T>() 
}

fun <T> funStreamOf(vararg elements: T): FunStream<T> = elements.toFunStream()

fun <T> Array<out T>.toFunStream(): FunStream<T> = when {
    this.isEmpty() -> FunStream.Nil
    else -> FunStream.Cons({ this[0] }, { this.copyOfRange(1, this.size).toFunStream() })
}


fun <T> FunStream<T>.getHead(): T = when(this) {

    FunStream.Nil -> throw NoSuchElementException()
    is FunStream.Cons -> head()
}

fun <T> FunStream<T>.getTail(): FunStream<T> = when(this) {
    FunStream.Nil -> throw NoSuchElementException()
    is FunStream.Cons -> tail()
}
  • 리스트 크기가 클 때, Lazy 컬렉션처럼 사용하는 것이 성능에 좋다


무한대 값 만들기

  • generateFunSream()은 함수를 입력받기때문에 즉시 평가 되지 않는다.
  • 함수들은 실제로 평가될 때 어떤 일들을 해야 하는지만을 기록하고 있을뿐이다.

fun <T> generateFunStream(seed: T, generate: (T) -> T) : FunStream<T> =
    FunStream.Cons({seed}, {generateFunStream(generate(seed), generate)})

tailrec fun <T> FunStream<T>.forEach(f: (T)->Unit): Unit = when(this) {
    FunStream.Nil -> Unit
    is FunStream.Cons -> {
        f(head())
        tail().forEach(f)
    }
}

val infiniteVal = generateFunStream(0) {
    it + 5
}
infiniteVal.forEach {
    println(it)
}


*개인적으로 코틀린을 공부하면서 정리한 자료입니다. 수정 사항 및 이슈가 있는 경우 메일 부탁드립니다.

댓글

이 블로그의 인기 게시물

코틀린 (Kotlin) filter, map, all, any, count, find, groupBy, flatMap 함수 정리

코틀린 (Kotlin) 인터페이스 정리

RecyclerView 에서 notifyItemChanged()의 payload 이해하기