2-2-34 快速掌握Kotlin-序列

33 阅读5分钟

Kotlin 序列(Sequence)深入学习

什么是序列?

序列(Sequence)是 Kotlin 中一种惰性求值的集合,类似于 Java 8 的 Stream。它按需计算元素,可以处理无限数据流,并且避免了中间集合的创建。

创建序列

1. sequenceOf() - 创建固定序列

val seq1 = sequenceOf(1, 2, 3, 4, 5)

2. asSequence() - 集合转序列

val list = listOf(1, 2, 3, 4, 5)
val seq2 = list.asSequence()

3. generateSequence() - 生成无限/有限序列

// 无限序列(从1开始,每次+1)
val infiniteSeq = generateSequence(1) { it + 1 }

// 有限序列(指定种子和终止条件)
val finiteSeq = generateSequence(1) { if (it < 10) it + 1 else null }

// 生成斐波那契数列
val fibonacci = generateSequence(Pair(0, 1)) {
    Pair(it.second, it.first + it.second)
}.map { it.first }

4. sequence{} - 使用构建器

val seq = sequence {
    yield(1)                    // 产生单个值
    yieldAll(listOf(2, 3, 4))   // 产生集合
    yieldAll(generateSequence(5) { it + 1 }.take(2))  // 产生其他序列
}
// 结果:1, 2, 3, 4, 5, 6

序列 vs 集合:关键区别

// 集合操作(急切求值)
val listResult = listOf(1, 2, 3, 4, 5)
    .map { 
        println("map: $it")
        it * it 
    }
    .filter { 
        println("filter: $it")
        it > 10 
    }
// 输出:所有元素先经过map,再经过filter

// 序列操作(惰性求值)
val seqResult = listOf(1, 2, 3, 4, 5)
    .asSequence()
    .map { 
        println("map: $it")
        it * it 
    }
    .filter { 
        println("filter: $it")
        it > 10 
    }
    .toList()  // 末端操作触发计算
// 输出:每个元素依次经过map和filter

中间操作(Intermediate Operations)

常用中间操作:

val seq = (1..10).asSequence()

// 1. map - 转换
val doubled = seq.map { it * 2 }

// 2. filter - 过滤
val even = seq.filter { it % 2 == 0 }

// 3. take / drop - 取/丢弃前n个
val first3 = seq.take(3)
val after3 = seq.drop(3)

// 4. takeWhile / dropWhile - 条件取/丢弃
val whileLessThan5 = seq.takeWhile { it < 5 }
val dropWhileLessThan5 = seq.dropWhile { it < 5 }

// 5. distinct - 去重
val distinctSeq = sequenceOf(1, 2, 2, 3, 3, 3).distinct()

// 6. sorted / sortedBy - 排序
val sorted = sequenceOf(3, 1, 4, 1, 5).sorted()
val sortedByLength = sequenceOf("apple", "banana", "kiwi")
    .sortedBy { it.length }

// 7. flatMap - 扁平化
val flatMapped = seq.flatMap { 
    sequenceOf(it, it * 10) 
}

操作顺序的重要性:

// 顺序影响性能
val numbers = (1..1_000_000).asSequence()

// 好的顺序:先过滤,再转换
val good = numbers
    .filter { it % 2 == 0 }      // 减少元素数量
    .map { it * it }              // 只对剩余元素计算
    .take(10)

// 差的顺序:先转换,再过滤
val bad = numbers
    .map { it * it }              // 对100万个元素计算
    .filter { it % 2 == 0 }       // 再过滤
    .take(10)

末端操作(Terminal Operations)

常用末端操作:

val seq = (1..10).asSequence()

// 1. 转换为集合
val list = seq.toList()
val set = seq.toSet()
val map = seq.associateWith { it * 2 }

// 2. 聚合操作
val sum = seq.sum()
val avg = seq.average()
val min = seq.minOrNull()
val max = seq.maxOrNull()
val count = seq.count()

// 3. 查找元素
val first = seq.first()
val firstEven = seq.first { it % 2 == 0 }
val last = seq.last()
val element = seq.elementAt(3)
val find = seq.find { it > 5 }

// 4. 判断
val any = seq.any { it > 5 }      // 是否有元素满足条件
val all = seq.all { it > 0 }      // 是否所有元素满足条件
val none = seq.none { it < 0 }    // 是否没有元素满足条件

// 5. 遍历
seq.forEach { println(it) }
seq.forEachIndexed { index, value -> 
    println("$index: $value") 
}

// 6. 规约
val sumReduce = seq.reduce { acc, value -> acc + value }
val sumFold = seq.fold(0) { acc, value -> acc + value }

高级序列操作

1. windowed - 滑动窗口

val seq = (1..5).asSequence()

val windows = seq.windowed(3)
// [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

val windowsPartial = seq.windowed(3, partialWindows = true)
// [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5], [5]]

2. zipWithNext - 相邻元素配对

val seq = (1..5).asSequence()

val pairs = seq.zipWithNext()
// [(1, 2), (2, 3), (3, 4), (4, 5)]

val transformed = seq.zipWithNext { a, b -> a + b }
// [3, 5, 7, 9]

3. chunked - 分块

val seq = (1..10).asSequence()

val chunks = seq.chunked(3)
// [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

val chunksTransformed = seq.chunked(3) { it.sum() }
// [6, 15, 24, 10]

4. plus / minus - 序列合并

val seq1 = sequenceOf(1, 2, 3)
val seq2 = sequenceOf(4, 5, 6)

val combined = seq1 + seq2          // 1, 2, 3, 4, 5, 6
val minus = seq1 - sequenceOf(2)    // 1, 3

无限序列处理

// 1. 无限序列的创建
val naturalNumbers = generateSequence(0) { it + 1 }

// 2. 限制无限序列
val first10 = naturalNumbers.take(10).toList()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// 3. 条件终止
val until100 = generateSequence(0) { 
    if (it < 100) it + 1 else null 
}

// 4. 生成质数序列
fun primes() = generateSequence(2) { current ->
    generateSequence(current + 1) { it + 1 }
        .first { candidate ->
            (2..Math.sqrt(candidate.toDouble()).toInt())
                .none { candidate % it == 0 }
        }
}

性能优化示例

示例1:处理大文件

fun processLargeFile(file: File) {
    val linesSequence = file.useLines { it.asSequence() }
    
    val result = linesSequence
        .filter { it.isNotBlank() }
        .map { it.trim() }
        .filter { it.startsWith("ERROR") }
        .take(100)  // 只取前100个错误
        .toList()   // 触发计算
}

示例2:分页查询模拟

fun pagedSequence(pageSize: Int = 10): Sequence<String> = sequence {
    var page = 0
    while (true) {
        val items = fetchPage(page, pageSize)  // 模拟数据库查询
        if (items.isEmpty()) break
        yieldAll(items)
        page++
    }
}

fun fetchPage(page: Int, size: Int): List<String> {
    // 模拟数据库分页查询
    val start = page * size
    val end = start + size
    return (start until end)
        .map { "Item $it" }
        .takeIf { it.isNotEmpty() } ?: emptyList()
}

自定义序列

// 自定义序列实现
class RangeSequence(
    private val start: Int,
    private val end: Int
) : Sequence<Int> {
    
    override fun iterator(): Iterator<Int> = object : Iterator<Int> {
        private var current = start
        
        override fun hasNext(): Boolean = current <= end
        
        override fun next(): Int {
            if (!hasNext()) throw NoSuchElementException()
            return current++
        }
    }
}

// 使用
val customSeq = RangeSequence(1, 5)
println(customSeq.toList())  // [1, 2, 3, 4, 5]

注意事项和最佳实践

何时使用序列:

  1. 大数据集(避免中间集合)
  2. 复杂操作链(多个map/filter操作)
  3. 需要惰性求值(按需计算)
  4. 无限数据流(如生成器模式)

何时使用集合:

  1. 小数据集(性能差异不大)
  2. 需要多次访问(序列每次计算都重新遍历)
  3. 需要索引访问(序列不支持直接索引)
  4. 简单操作(代码更清晰)

重要提示:

val seq = (1..5).asSequence()

// 错误:每次末端操作都会重新计算
val list1 = seq.toList()  // 计算一次
val list2 = seq.toList()  // 重新计算

// 正确:缓存结果
val cached = seq.toList()  // 计算并缓存
val use1 = cached
val use2 = cached

实战案例

案例1:搜索引擎建议

fun searchSuggestions(
    query: String, 
    dictionary: Sequence<String>
): List<String> {
    return dictionary
        .filter { it.startsWith(query, ignoreCase = true) }
        .sortedBy { it.length }  // 短词优先
        .take(10)                // 只取前10个
        .toList()
}

案例2:数据处理管道

fun processSensorData(data: Sequence<SensorReading>): AnalysisResult {
    return data
        .filter { it.quality == Quality.GOOD }
        .windowed(5, 1)  // 5个数据的滑动窗口
        .map { window ->
            val avg = window.map { it.value }.average()
            val stdDev = calculateStdDev(window.map { it.value })
            WindowAnalysis(avg, stdDev)
        }
        .filter { it.stdDev < 1.0 }  // 过滤稳定窗口
        .fold(AnalysisResult()) { acc, window ->
            acc.add(window)
        }
}

序列是 Kotlin 中强大的功能,特别适合函数式编程和数据处理场景。掌握序列可以帮助你写出更高效、更优雅的代码。