티스토리 뷰

728x90

 

 

왜 중단이 필요한지, 중단이 어떤 것인지 알아보기 위해 시퀀스의 작동 방식과 시퀀스 빌더를 알아보자.

 

 

시퀀스의 특징

 

코틀린에서는 제너레이터 대신 시퀀스를 생성 할 때 사용하는 시퀀스 빌더를 제공한다.

코틀린의 시퀀스는 List Set과 같은 컬렉션이랑 비슷한 개념이지만, 필요할 때마다 값을 하나씩 계산하는 지연(lazy) 처리를 한다.

  • 요구되는 연산을 최소한으로 수행한다.
  • 무한정이 될 수 있다. (-> while(true))
  • 메모리 사용이 효율적이다.

sequence()라는 함수를 이용해 값을 순차적으로 계산하고 필요할 때 반환하는 빌더를 정의한다.

퀀스의 람다 표현식 내부에서는 yield() 함수를 호출하여 시퀀스의 다음 값을 생성한다.

val seq = sequence {
    yield(l)
    yield(2)
    yield(3)
}

fun main() {
    for (num in seq) {
        print(num)
    } // 123
}

람다 내부의 수신 객체는 SequenceScope<T>다. 객체는 yield() 함수를 가지고 있다.

 

 

시퀀스 작동 방식

중요한 것은 각 숫자가 미리 생성되는 대신, 필요할 때마생성된다는 점이다.

시퀀스 빌더 내부와 시퀀스를 사용하는 곳에서 메시지를 출력하면 쉽게 이해할 수 있다.

val seq = sequence {
    println("Generating first")
    yield(1)
    println("Generating second")
    yield(2)
    println("Generating third")
    yield(3)
    println("Done")
}

fun main() {
    for (num in seq) {
        println("The next number is $num")
    }
}
 // Generating first
// The next number is 1
// Generating second
// The next number is 2
// Generating third
// The next number is 3
// Done

우선 첫 번째 수를 요청하면 빌더 부로 진입한다.

빌더에서 "Generating first”를 출력한 뒤, 숫자 1을 반환한다.

이후 반복문에서 반환된 값을 받은 뒤, “Next number is 1”을 출력한다.

 

여기서 시퀀스가 일반 컬렉션과 다른 결정적인 차이가 있다.

시퀀스 빌더가 멈췄던 지점에서 다시 실행이 된다는 점이다.

중단이 없으면 함수가 중간에 멈췄다가, 나중에 중단된 지점에서 다시 실행되는 건 불가능하다.

중단이 있기 문에 main 함수와 시퀀스 제너레이터가 번갈아가며 실행되는 것이다.

 

이를 더 자세히 보기 위해 시퀀스에서 일부분의 값만 요청해 보자.

val seq = sequence {
    println("Generating first")
    yield(1)
    printIn("Generating second")
    yield(2)
    println("Generating third")
    yield(3)
    println("Done")
}

fun main() {
    val iterator = seq.iterator()
    println("Starting")
    val first = iterator.next()
    println("First: $first")
    val second = iterator.next()
    println("Second: $second")
}
// Starting
// Generating first
// First: 1
// Generating second
// Second: 2

어떤 지점이든 iterator() 함수를 호출하면 빌더의 이전 지점으로 다시 돌아가 다음 값을 생성한다.

코루틴 없이 이런 가능할까?

물론 스레드가 일을 대신할 수도 있다. 하지만 중단을 지원하는 스레드로 처리하려면 유지하고 관리하는 비용이 크다.

코루틴을 사용하면 더 빠르고 간단하게 중단할 수 있다.

 

 

실제 사용 예시

전형적인 예시 중 하나는 피보나치 수열과 같은 수학적 시퀀스를 만드는 것이다.

val fibonacci: Sequence<BigInteger> = sequence { 
    var first = O.toBiglnteger()
    var second = 1.toBiglnteger()
    while (true) {
        yield(first)
        val temp = first
        first += second 
        second = temp
    }
}

fun main() {
    print(fibonacci.take(10).toList())
}
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

 

난수나 임의의 문자열을 만들 때도 사용할 수 있다.

fun randomNumbers(
    seed: Long = System.currentTimeMillis()
): Sequence<Int> = sequence {
    val random = Random(seed)
    while (true) {
        yield(random.nextlnt())
    }
}

fun randomUniqueStrings(
    length: Int,
    seed: Long = System.currentTimeMillis()
): Sequence<String> = sequence {
    val random = Random(seed)
    val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9')
    while (true) {
        val randomString = (1..length)
            .map { i -> random.nextlnt(charPool.size) }
            .map(charPool::get)
            .joinToString("")
        yield(randomString)
    }
}.distinct()

 

 

시퀀스 빌더 주의점

시퀀스 빌더 내부에서 yield() 함수가 아닌 다른 중단 함수를 사용하면 안 된다.

중단 함수의 호출이 필요하다면 추후에 배울 플로우를 사용하는 것이 좋다.

플로우 빌더가 작동하는 방식은 시퀀스 빌더와 비슷하지만, 플로우는 여러 코루틴 기능을 지원한다.

 

 

 

출처

https://www.yes24.com/product/goods/123034354

728x90
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/01   »
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 31
글 보관함