kotlinintermediate

Sequences — Lazy Evaluation for Large Data

Process large datasets efficiently with lazy sequences: generateSequence, yield, and custom iterators.

kotlin
fun main() {
    // Sequence vs List — lazy vs eager
    println("--- Eager (List) ---")
    val eagerResult = (1..10)
        .filter { print("filter($it) "); it % 2 == 0 }
        .map { print("map($it) "); it * 10 }
        .first()
    println("\nEager result: $eagerResult") // processes ALL then takes first

    println("\n--- Lazy (Sequence) ---")
    val lazyResult = (1..10).asSequence()
        .filter { print("filter($it) "); it % 2 == 0 }
        .map { print("map($it) "); it * 10 }
        .first()
    println("\nLazy result: $lazyResult") // stops at first match!

    // generateSequence
    println("\n--- Generate ---")
    val powers = generateSequence(1) { it * 2 }
    println("Powers of 2: ${powers.take(10).toList()}")

    // Fibonacci
    val fib = generateSequence(Pair(0L, 1L)) { (a, b) -> Pair(b, a + b) }
        .map { it.first }
    println("Fibonacci: ${fib.take(15).toList()}")

    // Collatz sequence
    fun collatz(n: Long) = generateSequence(n) { current ->
        when {
            current <= 1L -> null // terminate
            current % 2 == 0L -> current / 2
            else -> current * 3 + 1
        }
    }
    println("Collatz(27): ${collatz(27).toList().size} steps")

    // sequence builder with yield
    println("\n--- Yield ---")
    val custom = sequence {
        yield(1)
        yield(2)
        yieldAll(listOf(3, 4, 5))
        yieldAll(generateSequence(6) { it + 1 })
    }
    println("Custom: ${custom.take(10).toList()}")

    // File-like line processing (simulated)
    println("\n--- Processing ---")
    val lines = sequence {
        yield("# header")
        yield("name,age")
        yield("Alice,30")
        yield("Bob,25")
        yield("# comment")
        yield("Charlie,35")
    }

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

    val people = lines
        .filter { !it.startsWith("#") }
        .drop(1) // skip header
        .map { line ->
            val (name, age) = line.split(",")
            Person(name, age.toInt())
        }
        .toList()
    println("People: $people")

    // Chunked processing
    println("\n--- Chunked ---")
    (1..20).asSequence()
        .chunked(5)
        .forEach { chunk ->
            println("Processing batch: $chunk (sum=${chunk.sum()})")
        }

    // Benchmark: Sequence vs List for large data
    println("\n--- Benchmark ---")
    val size = 1_000_000
    val listTime = kotlin.system.measureTimeMillis {
        (1..size).filter { it % 3 == 0 }.map { it * 2L }.take(100).sum()
    }
    val seqTime = kotlin.system.measureTimeMillis {
        (1..size).asSequence().filter { it % 3 == 0 }.map { it * 2L }.take(100).sum()
    }
    println("List: ${listTime}ms, Sequence: ${seqTime}ms")
}

Use Cases

  • Processing large datasets without loading all into memory
  • Infinite series generation
  • Efficient pipeline processing with early termination

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.