티스토리 뷰

Kotlin

[Kotlin] 코틀린 시퀀스 (sequence)

woo'^'chang 2024. 3. 30. 13:46

코틀린에서는 Sequences라는 표준 라이브러리가 존재하는데 해당 라이브러리에 대해 알아보도록 하겠습니다.

Sequences

코틀린에는 일반적으로 동일한 유형 및 객체를 포함하는 Collections 표준 라이브러리가 존재합니다. List, Set, Map 타입이 존재하고, 인터페이스는 아래의 사진처럼 나타낼 수 있습니다.

코틀린에는 컬렉션과 함께 또 다른 표준 라이브러리인 Sequences가 있습니다. 코틀린 시퀀스는 요소를 포함하지 않고 반복하는 동안 요소를 생성합니다. 자바의 Stream처럼 지연(lazy) 연산이 가능하다는 것은 동일하지만, 몇 가지 차이점도 존재합니다.

  • stream 이후에 나온 sequence는 확장 함수를 사용해 정의되어 있어 더 많은 처리 함수를 가지고 있다.
  • stream은 멀티 코어 환경에서 병렬 처리가 가능하다.
  • sequence는 코틀린/JVM, 코틀린/JS, 코틀린/네이티브 환경에서 사용할 수 있지만, stream은 버전 8 이상의 코틀린/JVM 환경에서만 사용이 가능하다.

sequence의 주요 목적은 컬렉션 각 요소를 지연 처리하는 것으로, 주로 하나의 스레드에서 순차적으로 요소를 처리하는데 초점을 맞추고 있습니다. 순차적인 처리에서 병렬 처리는 결함 요소가 될 수 있기에 효율적인 단일 스레드 처리를 목적으로 합니다.

인터페이스 상으로는 Iterable, Sequence가 같은 형태를 가지고 있지만, 다른 목적으로 설계되었습니다. 차이점은 아래와 같습니다.

  • sequence는 element-by-element order로 연산을 처리하고, iterable은 step-by-step order로 연산을 처리한다.
  • sequence는 최종 연산이 일어나기 전에는 어떠한 처리도 하지 않는다. iterable은 중간 처리 단계에서 결과물을 계속해서 생성한다.
  • sequence는 무한 시퀀스를 만들고 필요한 부분까지 값을 추출하는 것이 가능하다.

element-by-element order vs step-by-step order

element-by-element order는 요소 각각의 처리를 완료하고 다음 단계로 넘어가는 것을 의미하고 step-by-step order는 모든 요소에 단계에 처리하는 연산을 적용한 뒤 다음 단계로 넘어가는 것을 말합니다.

 

(1, 2, 3, 4) 요소들에 대해 각 아이템을 출력하고 짝수인 아이템을 필터링해서 출력한 뒤, 11을 곱한 값을 출력하는 코드를 sequence, iterable 방법에 대해 각각 작성하면 아래와 같습니다.

// iterable
listOf(1, 2, 3, 4)
    .filter { println("[ITEM]: $it"); it % 2 == 0 }
    .map { println("[EVEN ITEM]: $it"); it * 11 }
    .forEach { println("[MAPPED ITEM]: $it") }
// sequence
listOf(1, 2, 3, 4).asSequence()
    .filter { println("[ITEM]: $it"); it % 2 == 0 }
    .map { println("[EVEN ITEM]: $it"); it * 11 }
    .forEach { println("[MAPPED ITEM]: $it") }

로직 상으로 동일하지만, 결과를 확인하면 차이가 있음을 알 수 있습니다.

iterable의 경우 모든 아이템이 각 스텝을 실행한 뒤 다음 스텝으로 넘어갔지만, sequence의 경우 아이템이 스텝의 조건을 충족할 때까지 수행한 뒤 다음 아이템 실행으로 넘어가는 것을 확인할 수 있습니다.

해당 과정을 도식화하면 위의 그림처럼 나타낼 수 있습니다. sequence는 아이템별로 실행되기에 short circuit을 통해 연산 성능을 높일 수 있는 것입니다.

시퀀스 생성

시퀀스는 여러 가지 방법으로 생성할 수 있습니다.

// (1)
val sequence1 = sequenceOf(1, 2, 3, 4)

// (2)
val sequence2 = listOf(1, 2, 3, 4).asSequence()

// (3)
val sequence3 = generateSequence(1) { it + 1 }

// (4)
val sequence4 = sequence {
    var sequence = generateSequence(1) { it + 1 }

    while (true) {
        val first = sequence.first()
        yield(first)
        sequence = sequence.drop(1)
    }
}

요소 목록으로 sequence를 바로 생성할 수도 있고 기존에 존재하는 iterable을 통해서도 생성할 수 있습니다.

 

(3), (4)는 무한한 요소를 생성할 수 있는 무한 시퀀스를 생성한 것인데 first(), take()와 같이 개수를 지정하는 연산과 함께 사용하지 않으면 프로그램이 종료되지 않거나 OutOfMemeoryException이 발생할 수 있기에 주의해서 사용이 필요합니다.

 

사용할 수 있는 중간 연산 및 최종 연산은 해당 링크에서 확인할 수 있습니다.

무한 시퀀스

무한 시퀀스에서는 yield(), yieldAll() 함수 호출을 통해 요소를 반환할 수 있습니다. 해당 함수들은 시퀀스 도중 요소를 시퀀스를 사용하는 소비자에게 반환하고 다음 요소를 요청할 때까지 시퀀스 실행을 일시 중단합니다.

val primes: Sequence<Int> = sequence {
    var numbers = generateSequence(2) { it + 1 }

    while (true) {
        val prime = numbers.first()
        yield(prime)
        numbers = numbers.drop(1)
            .filter { it % prime != 0 }
    }
}

무한하게 소수를 반환하도록 하는 시퀀스를 생성하는 코드를 위와 같이 작성할 수 있습니다. 중간에 yield() 함수를 통해 시퀀스를 사용하는 소비자가 요청할 때마다 소수를 반환하게 됩니다.

 

소수가 아닌 수와 한번 반환된 소수는 다시 반환하지 않도록 하기 위해 사용하는 시퀀스에 데코레이터 패턴으로 새로운 시퀀스를 생성해서 다음 요청을 기다립니다. filter에라토스테네스의 체 이론을 적용하여 소수가 아닌 수를 필터링하는 연산이고 drop은 한번 반환된 소수를 다시 반환하지 않도록 하기 위한 연산입니다.

 

소비자가 소수 3개를 요청한 흐름을 아래의 표로 정리할 수 있습니다.

description code
2부터 시작하는 무한 시퀀스 생성 var numbers = generateSequence(2) { it + 1 }
무한 시퀀스 첫번째 값인 2로 초기화 val prime = numbers.first()
2 반환 yield(prime)
데코레이터 패턴으로 새로운 시퀀스 초기화 numbers = numbers.drop(1)
                        .filter{ it % 2 != 0 }
2는 drop, 조건에 만족하는 3으로 초기화 val prime = numbers.drop(1)
                        .filter{ it % 2 != 0 }
                        .first()
3 반환 yield(prime)
데코레이터 패턴으로 새로운 시퀀스 초기화 numbers = numbers.drop(1)
                        .filter{ it % 2 != 0 }
                        .drop(1)
                        .filter{ it % 3 != 0 }
2, 3 drop, 4는 필터링, 조건에 만족하는 5로 초기화 val prime = numbers.drop(1)
                        .filter{ it % 2 != 0 }
                        .drop(1)
                        .filter{ it % 3 != 0 }
                        .first()
5 반환 yield(prime)

참고

'Kotlin' 카테고리의 다른 글

[Kotlin] 코틀린이란  (0) 2022.11.30
댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Total
Today
Yesterday