본문 바로가기
Kotlin/Kotlin 프로그래밍

[Kotlin] 코틀린의 시퀀스(Sequence)

by 주 녕 2021. 6. 2.
728x90

모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다. 

 

시퀀스(Sequence)

순차적인 컬렉션으로 요소의 크기를 특정하지 않고, 나중에 결정할 수 있는 특수한 컬렉션

 

 

요소 값 생성하기

generateSequence()

fun main() {
    val nums: Sequence<Int> = generateSequence(1) { it + 1 }

    println(nums.take(10).toList())
    // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    val squares = generateSequence(1) { it + 1 }.map {it*it}
    println(squares.take(10).toList())
    // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    val oddSquares = squares.filter { it % 2 != 0 }
    println(oddSquares.take(5).toList())
    // [1, 9, 25, 49, 81]
}
  • 특정 값을 생성하기 위해 generateSequence()를 사용
  • 시드(seed) 인수에 의해 시작 요소의 값이 결정됨
    • generateSequence(1)을 사용해 시드 인수로 1을 주고 1씩 증가하도록 시퀀스를 정의함
    • 객체 변수 nums에는 take(10)의 인자를 통해 10개의 요소가 저장됨
    • toList()를 통해 List 컬렉션으로 반환함
  • map, filter 같은 연산도 가능함
    • 새로운 List를 반환하는 메서드이기 때문에 메서드 체이닝을 쓴다면 중간결과로 새로운 List를 계속 만들어 냄

 

 

요소 값 가져오기

asSequence()를 통해 가져오기

fun main() {
    val list1 = listOf(1, 2, 3, 4, 5)
    val listDefault = list1
        .map { println("map($it) "); it * it}
        .filter { println("filter($it) "); it % 2 == 0}
    println(listDefault)

    val listSeq = list1.asSequence()
        .map { print("map($it) "); it * it }
        .filter { println("filter($it) "); it % 2 == 0 }
        .toList()
    println(listSeq)
}
  • 메서드 체이닝의 중간 결과 생성하는 방법
    • list1 컬렉션의 첫번째 연산은 map → 새로운 List가 반환됨
    • list1 컬렉션의 두번째 연산은 filter → 짝수만 걸러냄
    • map에 대한 처리가 끝난 후에 filter로 넘기기 때문에 실행 결과에서 중간 연산 결과가 존재함
  • asSequence()를 통해 가져오는 방법
    • 중간 결과 없이 끝까지 연산한 후 결과를 반환하는 방법 → 병렬처리를 하므로 성능이 좋음
    • 연산만으로는 결과를 도출할 수 없고 toList()를 해야 최정 결과를 List를 반환함
    • map의 수행 결과를 새로운 List에 만들고 이것을 다시 짝수인지 판별해 리스트를 만드는 과정이 없어짐 → 연속적으로 map과 filter가 각각 수행된 것을 확인할 수 있음
  • 역컴파일을 해보면
    • 기본 체이닝 : 2개의 while 루프를 돌면서 하나씩 컬렉션을 처리함
    • asSequence() : 내부적으로 iterator를 공유하며, map을 하나의 요소로 변경해 filter에 인자로 전달함

 

asSequence()의 시간 성능

fun main() {
    val listBench = (1..1_000_000).toList()
    timeElapsed {
        listBench
            .map { it + 1 }
            .first { it % 100 == 0 }
    }

    timeElapsed {
        listBench
            .asSequence()
            .map { it + 1 }
            .first { it % 100 == 0 }
    }
}

fun timeElapsed(task: () -> Unit) {
    val before = System.nanoTime()
    task()
    val after = System.nanoTime()
    val speed = (after - before) / 1_000
    println("$speed ns")
}

 

시스템마다 수행 결과는 약간씩 달라지겠지만 백만 개의 요소가 있는 목록을 처리하는 데 asSequence()를 사용하는 경우 비교적 짧은 시간에 수행된 것을 확인할 수 있음!

💡 다만 작은 컬렉션에서는 시뭔스를 사용하지 않는 것이 좋음!

👆 filter() 등은 인라인 함수로 설계되어 있는데, 시퀀스를 사용하면 람다식을 저장하는 객체로 표현되기 때문에 인라인되지 않아 작은 컬렉션에서는 오히려 좋지 않음! 그리고 한 번 계산된 내용은 메모리에 저장하기 때문에 시퀀스 자체를 인자로 넘기는 형태는 사용하지 않는 것이 좋음!

 

 

* 컬렉션의 map, filter에 대한 내용은 아래 포스팅 참고!

 

[Kotlin] 컬렉션(Collection)의 확장함수

모든 내용은 Do it! 안드로이드 앱 프로그래밍을 바탕으로 정리한 것입니다. [이전 포스팅] 코틀린의 컬렉션 - List, Set, Map에 대한 내용은 아래 포스팅 참고 [Kotlin] 컬렉션(Collection) - List 모든 내용

junyoung-developer.tistory.com

* 인라인 함수가 무엇인지 알고 싶다면 아래 포스팅 참고!

 

[Kotlin] 함수와 함수형 프로그래밍 (3)

모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다. 코틀린의 다양한 함수 익명 함수 (Anonymous Function) 이름이 없는 일반 함수 fun(x: Int, y: Int): Int = x + y val add1: (Int, Int) -> I..

junyoung-developer.tistory.com

 

 

728x90

댓글