Sequences - Kotlin

SeungHyuk Shin·2022년 3월 3일
0
post-thumbnail

코틀린 공식 문서 Sequences를 보고 번역한 글입니다.

Sequences

컬렉션과 함께 Kotlin 표준 라이브러리에는 다른 컨테이너 유형인 시퀀스(Sequence)가 포함되어 있습니다. 시퀀스는 Iterable과 동일한 기능을 제공하지만 다단계 수집 처리에 대한 또 다른 접근 방식을 구현합니다.

Iterable의 처리에 여러 단계가 포함되어 있으면 즉시 실행됩니다. 각 처리 단계는 완료되고 그 결과인 중간 컬렉션을 반환합니다. 이 컬렉션에서 다음 단계가 실행됩니다. 차례로, 시퀀스의 다단계 처리는 가능한 경우 느리게 실행됩니다. 실제 계산은 전체 처리 체인의 결과가 요청될 때만 발생합니다.

작업 실행 순서도 다릅니다. Sequence는 모든 단일 요소에 대해 모든 처리 단계를 하나씩 수행합니다. 정해진 순서대로 Iterable은 전체 컬렉션에 대한 각 단계를 완료하고 다음 단계로 진행합니다.

따라서 시퀀스를 사용하면 중간 단계의 결과 작성을 피할 수 있으므로 전체 컬렉션 처리 체인의 성능이 향상됩니다. 그러나 시퀀스의 게으른 특성은 더 작은 컬렉션을 처리하거나 더 간단한 계산을 수행할 때 중요할 수 있는 약간의 오버헤드를 추가합니다. 따라서 Sequence와 Iterable을 모두 고려하고 어느 것이 경우에 더 나은지 결정해야 합니다.

Construct

From elements

시퀀스를 만들려면 요소를 인수로 나열하는 sequenceOf() 함수를 호출합니다.

val numbersSequence = sequenceOf("four", "three", "two", "one")

From an Iterable

이미 Iterable 객체(예: List 또는 Set)가 있는 경우 asSequence()를 호출하여 이 객체에서 시퀀스를 생성할 수 있습니다.

val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()

From a function

시퀀스를 만드는 또 다른 방법은 요소를 계산하는 함수로 시퀀스를 만드는 것입니다. 함수를 기반으로 시퀀스를 작성하려면 이 함수를 인수로 사용하여 generateSequence()를 호출하십시오. 선택적으로 첫 번째 요소를 명시적 값이나 함수 호출의 결과로 지정할 수 있습니다. 제공된 함수가 null을 반환하면 시퀀스 생성이 중지됩니다. 따라서 아래 예제의 시퀀스는 무한합니다.

val oddNumbers = generateSequence(1) { it + 2 } // `it` is the previous element
println(oddNumbers.take(5).toList())
// println(oddNumbers.count())     // error: the sequence is infinite

generateSequence()로 유한 시퀀스를 만들려면 필요한 마지막 요소 뒤에 null을 반환하는 함수를 제공해야 합니다.

val oddNumbersLessThan10 = generateSequence(1) { if (it < 8) it + 2 else null }
println(oddNumbersLessThan10.count())

From chunks

마지막으로 시퀀스 요소를 하나씩 또는 임의 크기의 청크로 생성할 수 있는 함수인 sequence() 함수가 있습니다. 이 함수는 yield()yieldAll() 함수 호출을 포함하는 람다 식을 사용합니다. 시퀀스 소비자에게 요소를 반환하고 소비자가 다음 요소를 요청할 때까지 sequence() 실행을 일시 중단합니다. yield()는 단일 요소를 인수로 사용합니다. yieldAll()은 Iterable 객체, Iterator 또는 다른 시퀀스를 취할 수 있습니다. yieldAll()의 시퀀스 인수는 무한할 수 있습니다. 그러나 이러한 호출은 마지막 호출이어야 합니다. 모든 후속 호출은 실행되지 않습니다.

val oddNumbers = sequence {
    yield(1)
    yieldAll(listOf(3, 5))
    yieldAll(generateSequence(7) { it + 2 })
}
println(oddNumbers.take(5).toList())

Sequence operations

시퀀스 작업은 상태 요구 사항과 관련하여 다음 그룹으로 분류할 수 있습니다.

  • Stateless 작업에는 상태가 필요하지 않으며 map() 또는 filter()와 같이 각 요소를 독립적으로 처리합니다. Stateless 작업은 또한 요소를 처리하기 위해 소량의 일정한 상태를 요구할 수 있습니다(예: take() 또는 drop()).
  • Stateful 작업에는 일반적으로 시퀀스의 요소 수에 비례하는 상당한 양의 상태가 필요합니다.

시퀀스 작업이 느리게 생성되는 다른 시퀀스를 반환하는 경우 이를 intermediate이라고 합니다. 그렇지 않으면 작업은 terminal입니다. terminal 작업의 예는 toList() 또는 sum()입니다. 시퀀스 요소는 terminal 작업으로만 검색할 수 있습니다.

시퀀스는 여러 번 반복될 수 있습니다. 그러나 일부 시퀀스 구현은 한 번만 반복되도록 스스로를 제한할 수 있습니다. 이는 문서에 구체적으로 언급되어 있습니다.

Sequence processing example

예를 들어 Itable과 Sequence의 차이를 살펴보겠습니다.

Iterable

단어 목록이 있다고 가정합니다. 아래 코드는 3자 이상의 단어를 필터링하고 이러한 단어의 처음 네 개의 길이를 인쇄합니다.

val words = "The quick brown fox jumps over the lazy dog".split(" ")
val lengthsList = words.filter { println("filter: $it"); it.length > 3 }
  .map { println("length: ${it.length}"); it.length }
  .take(4)

println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)

----out put------

filter: The
filter: quick
filter: brown
filter: fox
filter: jumps
filter: over
filter: the
filter: lazy
filter: dog
length: 5
length: 5
length: 5
length: 4
length: 4
Lengths of first 4 words longer than 3 chars:
[5, 5, 5, 4]

이 코드를 실행하면 filter() 및 map() 함수가 코드에 표시된 것과 동일한 순서로 실행되는 것을 볼 수 있습니다. 먼저 모든 요소에 대해 filter:가 표시되고 필터링 후 남은 요소에 대해 length:가 표시되고 마지막 두 줄의 출력이 표시됩니다.

리스트의 처리 방법은 다음과 같습니다.

Sequence

이제 시퀀스로도 동일한 내용을 작성해보겠습니다.

val words = "The quick brown fox jumps over the lazy dog".split(" ")
//convert the List to a Sequence
val wordsSequence = words.asSequence()

val lengthsSequence = wordsSequence.filter { println("filter: $it"); it.length > 3 }
    .map { println("length: ${it.length}"); it.length }
    .take(4)

println("Lengths of first 4 words longer than 3 chars")
// terminal operation: obtaining the result as a List
println(lengthsSequence.toList())

---- output ----

Lengths of first 4 words longer than 3 chars
filter: The
filter: quick
length: 5
filter: brown
length: 5
filter: fox
filter: jumps
length: 5
filter: over
length: 4
[5, 5, 5, 4]

이 코드의 출력은 filter() 및 map() 함수가 결과 목록을 작성할 때만 호출된다는 것을 보여줍니다. 따라서 먼저 "Length of.."라는 텍스트 행이 표시된 다음 시퀀스 처리가 시작됩니다. 필터링 후 남은 요소의 경우 맵은 다음 요소를 필터링하기 전에 실행됩니다. 결과 크기가 4에 도달하면 take(4)가 반환할 수 있는 가장 큰 크기이기 때문에 처리가 중지됩니다.

시퀀스 처리는 다음과 같이 진행됩니다.

이 예에서 시퀀스 처리는 목록과 동일한 작업을 수행하기 위해 23단계 대신 18단계를 수행합니다.

0개의 댓글