集合的分组(Grouping)
在之前的学习中,我们已经学会了如何对集合进行过滤、排序或执行聚合操作。
在本节中,我们将学习如何对集合元素进行分组,以便以最适合我们任务的方式呈现信息。
分组(Grouping)
在 Kotlin 中,有一些扩展函数可以用来对集合元素进行分组,其中一个就是 groupBy()
。它接收一个 lambda 表达式,并返回一个 Map,其中的键(key)是分组依据,值(value)则是对应的集合元素组成的列表。
fun main() {
val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
// 按名字的首字母进行分组
val groupedNames = names.groupBy { it.first() }
println(groupedNames) // {J=[John, Jane, John, Jane], M=[Mary, Mary], P=[Peter, Peter]}
}
代码解释:
在上面的示例中,我们按名字的首字母进行分组:可以看到返回的 Map 中,键是名字的首字母,值是所有以该字母开头的名字列表。例如,键 J
对应的值是 [John, Jane, John, Jane]
。
你还可以在 groupBy()
中传入第二个 lambda 作为转换函数(valueTransform)。这样就能在分组时同时对元素进行变换。如下所示,我们将分组的元素转换为大写:
fun main() {
val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
// 按名字长度分组,并将每个名字转为大写fun main() {
val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
// 按名字长度分组,并将每个名字转为大写
val groupedNames2 = names.groupBy(keySelector = { it.length },
valueTransform = { it.uppercase() })
println(groupedNames2) // {4=[JOHN, JANE, MARY, JOHN, JANE, MARY], 5=[PETER, PETER]}
}
val groupedNames2 = names.groupBy(
keySelector = { it.length }, // 分组键:名字长度
valueTransform = { it.uppercase() } // 值变换:转为大写
)
println(groupedNames2)
// 输出:{4=[JOHN, JANE, MARY, JOHN, JANE, MARY], 5=[PETER, PETER]}
}
分组与附加操作
有时我们想对所有分组同时进行某种操作。我们可以使用 groupingBy()
方法,它返回一个 Grouping 实例,允许以“懒方式”对各组进行操作(即在执行操作前不会真正构建分组)。
常见方法:
-
eachCount()
:计算每组中元素的数量,返回一个 Map,键是分组键,值是该组的元素数量。 -
fold()
:带有初始值,从左到右依次将操作应用于累加器与每个元素。如果集合为空,返回初始值。
可以提供一个initialValueSelector
函数用于设置初始值。.fold(initialValue) { acc, element -> ... }
-
reduce()
:从第一元素开始进行累加操作(没有初始值),如果集合为空会抛出异常。可使用reduceOrNull()
以防止异常。.groupingBy { ... }.reduce { key, acc, element -> ... }
fun main() {
val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
// 按首字母分组,并统计每组数量
val groupedNames3 = names.groupingBy { it.first() }.eachCount()
println(groupedNames3) // {J=4, M=2, P=2}
// 按首字母分组,累加每组中名字的总长度
val groupedNames4 = names.groupingBy { it.first() }
.fold(0) { acc, name -> acc + name.length }
println(groupedNames4) // {J=16, M=8, P=10}
// 按名字长度分组,保留每组中最长的名字
val groupedNames5 = names.groupingBy { it.length }
.reduce { _, acc, name -> if (name.length > acc.length) name else acc }
println(groupedNames5) // {4=John, 5=Peter}
}
使用 aggregate() 聚合
使用 aggregate()
函数,可以对每个分组应用操作并返回结果。它提供了一种通用方式来执行分组操作(在 fold
和 reduce
不满足需求时)。
语法:
aggregate { key, accumulator: R?, element, first ->
// 返回新的 accumulator(累积值)
}
-
key
:当前分组的键,比如J
,M
,P
。 -
accumulator
:上一次累积的值,第一次为null
。 -
element
:当前正在处理的元素(比如"John"
)。 -
first
:是否是该组的第一个元素。
示例:
fun main() {
val names = listOf("John", "Jane", "Mary", "Peter", "John", "Jane", "Mary", "Peter")
// 使用 aggregate 获取每组的元素数量
val groupedNames6 = names.groupingBy { it.first() }
.aggregate { _, accumulator: Int?, _, first ->
if (first) 1 else accumulator!! + 1
}
println(groupedNames6) // {J=4, M=2, P=2}
// 判断每组中所有名字的长度是否都是偶数
val groupedNames7 = names.groupingBy { it.first() }
.aggregate { _, accumulator: Boolean?, element, first ->
if (first) element.length % 2 == 0 else accumulator!! && element.length % 2 == 0
}
println(groupedNames7) // {J=true, M=true, P=false}
}
总结
在本节中,我们学习了如何使用 groupBy
和 groupingBy
函数对集合中的元素进行分组。这是处理集合数据时非常重要的技能。