1. 简介
假设我们有一个列表,例如 listOf(a, b, c, d, e, f)
,我们希望将其拆分成多个子列表,例如:listOf(listOf(a, b), listOf(c, d), listOf(e, f))
或者 listOf(listOf(a, b, c), listOf(d), listOf(e, f))
。
在本教程中,我们将使用 Kotlin 中的 groupBy
、chunked
和 windowed
方法实现这一功能,并比较它们之间的差异。
2. 将列表拆分为成对的子列表
我们使用两个列表作为示例:一个元素数量为偶数,一个为奇数:
val evenList = listOf(0, "a", 1, "b", 2, "c")
val unevenList = listOf(0, "a", 1, "b", 2, "c", 3)
我们希望将 evenList
拆分为三个包含两个元素的子列表,而 unevenList
则会多出一个单独元素。
2.1. 使用 groupBy
我们可以使用 groupBy
实现列表拆分。例如:
val numberList = listOf(1, 2, 3, 4, 5, 6)
numberList.groupBy { (it + 1) / 2 }.values
输出结果为:
[[1, 2], [3, 4], [5, 6]]
原理是:groupBy
会为每个元素计算一个分组键,相同键的元素会被归为一组。
但如果我们使用一个不连续的列表,例如:
val numberList = listOf(1, 3, 8, 20, 23, 30)
numberList.groupBy { (it + 1) / 2 }.values
结果会是:
[[1], [3], [8], [20], [23], [30]]
因为每个元素的分组键都不相同,所以无法形成有效分组。而且如果元素类型不是数字,这段代码甚至无法编译。
2.2. 使用 withIndex() + groupBy
更通用的方法是基于索引分组:
evenList.withIndex()
.groupBy { it.index / 2 }
.map { it.value.map { it.value } }
输出结果为:
[[0, "a"], [1, "b"], [2, "c"]]
对于 unevenList
,也能正确处理最后一个单独元素:
[[0, "a"], [1, "b"], [2, "c"], [3]]
2.3. 使用 foldIndexed
我们也可以使用 foldIndexed
实现更高效的拆分逻辑:
evenList.foldIndexed(ArrayList<ArrayList<Any>>(evenList.size / 2)) { index, acc, item ->
if (index % 2 == 0) {
acc.add(ArrayList(2))
}
acc.last().add(item)
acc
}
虽然代码更长,但这种方式避免了创建额外的索引包装对象,性能更优。
2.4. 使用 chunked
Kotlin 标准库提供了更简洁的方法:chunked
,它直接按固定大小拆分列表:
evenList.chunked(2)
输出:
[[0, "a"], [1, "b"], [2, "c"]]
对于 unevenList
,也能正确包含最后一个元素:
[[0, "a"], [1, "b"], [2, "c"], [3]]
✅ 优点:简洁、直观、无需手动处理索引逻辑。
2.5. 使用 windowed
windowed
提供了更灵活的窗口滑动功能,适用于需要更细粒度控制的场景:
evenList.windowed(2, 2)
默认不包含部分窗口,输出:
[[0, "a"], [1, "b"], [2, "c"]]
若希望包含部分窗口,可设置 partialWindows = true
:
unevenList.windowed(2, 2, true)
输出:
[[0, "a"], [1, "b"], [2, "c"], [3]]
⚠️ 踩坑提醒:windowed
的参数顺序是 size, step, partialWindows
,容易搞错顺序。
3. 关于 partition()
方法
除了将列表拆分成固定大小的子列表,有时我们还需要根据某个条件将列表划分为两个部分。这时可以使用 partition()
:
val numbers = listOf(42, 1984, 1, 0, -4, 23, 100, 6, 8)
val (lessThan42, greaterThanOrEq42) = numbers.partition { it < 42 }
assertEquals(listOf(1, 0, -4, 23, 6, 8), lessThan42)
assertEquals(listOf(42, 1984, 100), greaterThanOrEq42)
✅ 优点:语法简洁,支持解构赋值,非常适合二分场景。
4. 总结
方法 | 优点 | 缺点 |
---|---|---|
groupBy |
灵活、可自定义分组逻辑 | 易出错、需手动处理索引或键 |
withIndex |
更通用、可基于索引分组 | 代码稍复杂 |
foldIndexed |
性能更优 | 代码冗长 |
chunked |
简洁、直观、标准库推荐方式 | 功能有限 |
windowed |
支持滑动窗口、可控制是否包含部分 | 灵活但略复杂,参数易混淆 |
partition |
适合二分场景,支持解构赋值 | 仅适用于条件划分 |
✅ 最佳实践建议:
- 优先使用
chunked
,它是最简洁且标准推荐的方式。 - 如果需要更复杂的窗口控制,使用
windowed
。 - 如果需要基于条件将列表分为两部分,使用
partition()
。 groupBy
和foldIndexed
更适合需要高度定制的场景,但要注意代码可读性和性能平衡。
完整的示例代码可以在 GitHub 上找到。