Kotlin中groupBy和groupingBy使用中的对比

本文比较了Kotlin中List聚合操作的groupBy与groupingBy方法,展示了groupingBy在性能上的优势,并通过实例演示了如何利用groupingBy减少遍历次数。作者通过性能测试对比,建议在高并发场景下优先选择groupingBy。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天在看Kotlin的Coroutines官方文档练英文的时候,它有个Hands-on,在里面要实现一个aggregate的功能,先来看看我的初版实现:

fun List<User>.aggregate(): List<User> {
    val map = this.groupBy { it.login }
    val result = ArrayList<User>(map.keys.size)
    for (login in map.keys) {
        result.add(User(login, map[login]?.sumOf { it.contributions } ?: 0))
    }
    result.sortByDescending { it.contributions }
    return result
}

再来看看官方的实现:

fun List<User>.aggregate(): List<User>  = groupBy { it.login }
    .map { (k,v)-> User(k, v.sumOf { it.contributions })}
    .sortedByDescending { it.contributions }

我自己的实现和官方的解决方案都用了groupBy,这个函数的功能就是根据给定的key遍历list进行分组:

/**
 * Groups elements of the original collection by the key returned by the given [keySelector] function
 * applied to each element and returns a map where each group key is associated with a list of corresponding elements.
 * 
 * The returned map preserves the entry iteration order of the keys produced from the original collection.
 * 
 * @sample samples.collections.Collections.Transformations.groupBy
 */
public inline fun <T, K> Iterable<T>.groupBy(keySelector: (T) -> K): Map<K, List<T>> {
    return groupByTo(LinkedHashMap<K, MutableList<T>>(), keySelector)
}

相比官方我没有用map而是用了一个局部变量来接收groupBy的结果供后续处理,排序也没有注意到有一个sortByDescending还有一个sortedByDescending,明显不如官方的简练.

但是在文章中后面又说:An alternative is to use the functiongroupingByinstead ofgroupBy.😳😳😳还有一个groupingBy`吗?之前都没有注意到有这个东西,让我来康康:

/**
 * Creates a [Grouping] source from a collection to be used later with one of group-and-fold operations
 * using the specified [keySelector] function to extract a key from each element.
 * 
 * @sample samples.collections.Grouping.groupingByEachCount
 */
@SinceKotlin("1.1")
public inline fun <T, K> Iterable<T>.groupingBy(crossinline keySelector: (T) -> K): Grouping<T, K> {
    return object : Grouping<T, K> {
        override fun sourceIterator(): Iterator<T> = this@groupingBy.iterator()
        override fun keyOf(element: T): K = keySelector(element)
    }
}

看代码这里并没有进行遍历操作而是直接返回了一个Grouping类型的源数据的界面.不处理数据直接交给后续的操作处理.在这个例子中相对于groupBy可以减少一次遍历.那用groupingBy如何实现呢?

fun List<User>.aggregateFromGrouping(): List<User> = groupingBy { it.login }
    .aggregate<User, String, Int> { _, accumulator, element, _ ->
        element.contributions + (accumulator ?: 0)
    }
    .map { (k, v) -> User(k, v) }
    .sortedByDescending { it.contributions }

看起来比groupBy复杂一点点,在aggregate这真正的遍历操作数据的这一步这里需要要繁杂一点点,但是因为少了一次遍历,在我的电脑上重复一千万次会比使用groupBy快一些:

 println("start groupingBy")
 val s2 = System.currentTimeMillis()
 repeat(10000000){
     actual = list.aggregateFromGrouping()
 }
 println("end groupingBy ${System.currentTimeMillis()-s2}ms")

 println("start groupBy")
 val s1 = System.currentTimeMillis()
 repeat(10000000){
     actual = list.aggregate()
 }
 println("end groupBy ${System.currentTimeMillis()-s1}ms")

-------------------------------result-------------------------
start groupingBy
end groupingBy 2064ms
start groupBy
end groupBy 2439ms

综上在性能要求严格的情况下推荐使用groupingBy.

水完,下班.🚍

### Kotlin 中 `groupBy` 函数的用法 在 Kotlin 的标准库中,`groupBy` 是一种非常强大的高阶函数,用于将集合中的元素按照指定的标准分组。它返回一个映射 (`Map`),其中键表示分组依据,而值是一个列表,包含属于该组的所有元素。 以下是关于 `groupBy` 的详细介绍: #### 基本语法 `groupBy` 可以通过 lambda 表达式定义分组逻辑。其基本形式如下: ```kotlin collection.groupBy { keySelector } ``` - **keySelector**: 定义如何计算每个元素对应的键。 如果需要更复杂的处理,可以使用另一种重载版本: ```kotlin collection.groupBy({ keySelector }, { valueTransform }) ``` - **valueTransform**: 对原始集合中的每个元素应用转换操作后再存储到结果 Map 中。 --- #### 示例代码 ##### 示例 1: 使用简单的 `groupBy` 假设有一个整数列表,我们希望按奇偶性对其进行分组: ```kotlin val numbers = listOf(1, 2, 3, 4, 5, 6) val groupedNumbers = numbers.groupBy { it % 2 == 0 } println(groupedNumbers) // 输出: {false=[1, 3, 5], true=[2, 4, 6]} ``` 这里,`it % 2 == 0` 返回布尔值作为键,分别代表奇数偶数组合[^1]。 --- ##### 示例 2: 结合复杂对象进行分组 考虑一组学生数据结构,我们需要根据学生的年级对他们进行分类: ```kotlin data class Student(val name: String, val grade: Int) val students = listOf( Student("Alice", 1), Student("Bob", 2), Student("Charlie", 1), Student("David", 2) ) val groupedStudents = students.groupBy { it.grade } println(groupedStudents) // 输出: {1=[Student(name=Alice, grade=1), Student(name=Charlie, grade=1)], // 2=[Student(name=Bob, grade=2), Student(name=David, grade=2)]} ``` 此示例展示了如何基于对象属性创建分组[^2]。 --- ##### 示例 3: 应用额外变换 (valueTransform) 如果我们不仅想分组,还想对每项执行某些修改,则可利用第二个参数来实现这一点: ```kotlin val words = listOf("apple", "banana", "cherry", "date") val transformedGroup = words.groupBy({ it.first().toLowerCase() }) { it.uppercase() } println(transformedGroup) // 输出: {a=[APPLE], b=[BANANA], c=[CHERRY], d=[DATE]} ``` 在此例子中,字母被设为首字符的小写形式作为键;同时字符串本身则转成大写字母存入对应列表中。 --- ### 性能注意事项 尽管 `groupBy` 提供了极大的灵活性,但在大规模数据集上可能会带来性能开销。这是因为每次调用都会构建一个新的中间数据结构——即哈希表(HashMap)。因此,在实际开发过程中应权衡效率需求与功能便利之间的关系。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值