지연 계산 컬렉션 연산: 시퀀스

유우선·2026년 2월 4일

Kotlin Study📚

목록 보기
14/32

개요

  • map, filter의 연쇄적 호출 → 결과 컬렉션을 즉시 생성
  • 컬렉션 함수 연쇄 사용 → 매 단계마다 계산 중간 결과를 새로운 컬렉션에 임시로 저장
  • 시퀀스 → 중간 임시 컬렉션을 사용하지 않고 연쇄 연산을 진행하는 방법을 제공
  • 시퀀스 → Sequence 인터페이스에서 시작
    • Sequence 인터페이스에는 iterator라는 메서드만 있음
    • 시퀀스의 원소 → lazy 하게 연산
  • asSequence 확장 함수를 호출하면 어떤 컬렉션이든 시퀀스로 바꿀 수 있음
  • 원소를 인덱스를 사용해 접근해야 한다면 시퀀스를 다시 컬렉션으로 변환해야 함

시퀀스 연산 실행: 중간 연산과 최종 연산

중간 연산

  • 최초 시커스의 원소를 변환하는 방법을 제공

최종 연산

  • 최초 시퀀스에 대해 변환을 적용한 시퀀스에서 일련의 계산을 수행해 얻은 객체

최종 연산이 없는 시퀀스

fun main() {
    println(
        listOf(1, 2, 3, 4)
            .asSequence()
            .map {
                print("map($it)")
                it * it
            }
            .filter {
                print("filter($it)")
                it % 2 == 0
            }
    )
    // kotlin.sequences.FilteringSequence@37a71e93
}
  • Sequence의 원소가 출력되는 대신 Sequence 객체 자체에 대해 출력됨
    • map, filter 연산이 지연됐기 때문
    • map, filter 연산은 최종 연산이 호출될 때 적용됨

최종 연산 적용

fun main() {
    println(
        listOf(1, 2, 3, 4)
            .asSequence()
            .map {
                print("map($it)")
                it * it
            }
            .filter {
                print("filter($it)")
                it % 2 == 0
            }.toList() // 최종 연산
    )
    // map(1)filter(1)map(2)filter(4)map(3)filter(9)map(4)filter(16)[4, 16]
}
  • 시퀀스에 대한 map, filter 연산 → 각 원소에 대해 순차적으로 적용
    • 원소에 연산을 차례대로 적용하다가 결과가 얻어지면 그 이후의 원소에 대해서는 변환이 이뤄지지 않을 수 있음

중간 연산 중 종료

fun main() {
    println(
        listOf(1, 2, 3, 4)
            .asSequence()
            .map { it * it }
            .find { it > 3 }
    )
    // 4
}

/*
find는 조건에 맞는 첫번째 원소를 반환하기 때문에 2번째 원소에 대한 연산 단계에서
조건에 맞는 값을 발견하여 이후 연산은 진행되지 않음
*/
  • 지연 계산으로 인해 원소 중 일부의 계산이 이뤄지지 않음

연산 수행 순서에 따른 성능 차이

data class Person(val name: String, val age: Int)

fun main() {
    val people = listOf(
        Person("Alice", 29),
        Person("Bob", 31),
        Person("Charles", 31),
        Person("Dan", 21)
    )
    println(
        people
            .asSequence()
            .map(Person::name)
            .filter { it.length < 4 }
            .toList()
    )
    // [Bob, Dan]
    println(
        people
            .asSequence()
            .filter {it.name.length < 4}
            .map(Person::name)
            .toList()
    )
    // [Bob, Dan]
}
  • 두 시퀀스의 결과는 같음
  • 하지만 중간 연산에서 변환 횟수가 다름

  • filter를 먼저 수행하면 필요없는 원소가 먼저 걸러지기 때문에 성능이 좀 더 좋음

시퀀스 만들기

시퀀스를 만드는 방법 : asSequence(), generateSequence()

  • generateSequence함수 → 이전의 원소를 인자로 받아 다음 원소를 계산
fun main() {
    val naturalNumber = generateSequence(0) { it + 1 } // 모든 자연수에 대한 시퀀스 생성
    val numberTo100 = naturalNumber.takeWhile { it <= 100 } // 100보다 작거나 같은 원소만 남김
    println(numberTo100.sum())
    // 5050
}
  • naturalNumber, numberTo100 → 최종 연산 전까진 시퀀스의 각 수는 연산이 지연됨

조상 디렉터리의 시퀀스를 생성하고 사용하기

import java.io.File

fun File.isInsideHiddenDirectory() =
    generateSequence(this) { it.parentFile }.any{ it.isHidden }

fun main() {
    val file = File("/Users/svtk/.HiddenDir/a.txt")
    println(file.isInsideHiddenDirectory())
    // true
}

0개의 댓글