侧边栏壁纸
博主头像
Z同学博主等级

工作磨平激情前,坚持技术的热忱。 欢迎光临Z同学的技术小站。 分享最新的互联网知识。

  • 累计撰写 274 篇文章
  • 累计创建 55 个标签
  • 累计收到 74 条评论

Kotlin 集合 plus,minus和分组group详解

Z同学
2021-10-09 / 0 评论 / 2 点赞 / 926 阅读 / 8,236 字
温馨提示:
本文最后更新于 2021-11-18,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

前言

本文是针对kotlin集合的第三篇,继续深入学习关于kotlin集合的使用,学习如何快捷插入数据,plus和minus

分组操作,自定义分组输出等等。

为方便跳转,贴一下前两篇文章的链接

Kotlin 集合 转换,过滤和检测 - Z同学 (zinyan.com)

Kotlin 集合 基本介绍 - Z同学 (zinyan.com)

加减操作:plus 和minus 操作符

int型数据中,我们通过+ 或者- 可以直接针对两个数值进行计算操作。

而针对集合对象,java只能调用相应的add 或者remove等函数进行添加或者移除操作。

而针对集成的常用操作,添加和删除。Kotlin定义了plus和minus 的操作符 分别为:plus --> +

minus --> -

关于操作符的介绍Kotlin之中的操作符 - Z同学 (zinyan.com) 可以看这篇介绍。

概括就是,我们可以直接使用+或者- 符号来替代plus函数和minus函数。

plus 加号操作

示例 plus:

fun main(string: Array<String>) {
    val num = listOf("A", "B", "C", "D", "E", "F")
    val zz = num+"ZInyan"
    println(zz)
}
//输出
[A, B, C, D, E, F, ZInyan]

上面的方法 其实和下面方法是一样的:

示例 plus:

fun main(string: Array<String>) {
    val num = listOf("A", "B", "C", "D", "E", "F")
    val zzz = num.plus("zzzz")
    println(zzz)
}
//输出
[A, B, C, D, E, F, zzzz]

只是使用加号(+) 替代了plus函数而已。

除了可以添加对象,还可以在后面添加集合。

示例 plus :

fun main(string: Array<String>) {
    val n1 = listOf("A", "B", "C", "D", "E", "F")
    val n2= listOf("1","2","3")
    val zz = n1+n2
    println(zz)
}
//输出
[A, B, C, D, E, F, 1, 2, 3]

总结:plus操作可以将一个集合与另一个元素,或者集合组合成一个新的只读集合

minus 减号操作

示例:

fun main(string: Array<String>) {
    val n1 = listOf("A", "B", "C", "D", "E", "F")
    val n2 = listOf("A", "B", "X")
    val zz = n1 - n2
    println(zz)
}
//输出
[C, D, E, F]

注意,不管是plus还是minus 操作之后得到的都是一个新的集合,原集合不会产生变化。

如果,集合减去的是另外一个集合,和减去的是一个元素。两种情况下计算方式有较大差别

示例:

fun main(string: Array<String>) {
    val n1 = listOf("A", "A", "A", "D", "E", "F")
    val n2 = listOf("A", "B", "X")
    //减去一个集合对象
    val zz = n1 - n2
    println(zz)
	//减去一个元素对象
    val zz2 = n1-"A"
    println(zz2)
}
//输出
[D, E, F]
[A, A, D, E, F]

通过对比输出及结果,我们可以明显看到
如果是移除一个元素对象,那么minus会移除原始集合中的该元素的第一次,并且只会移除一次。

如果是移除一个集合对象,那么minus会移除原始集合中的所有存在的元素。

在Map中的特殊定义

因为map是键值对的关系,所以plus和minus 操作符的使用场景,有别与其他的集合对象。

下面主要介绍下,map情况下的plus和minus操作符。先通过示例代码来了解一下。

fun main(string: Array<String>) {
    //我们定义了一个 key 是int,value 是字符的map 对象
    val n1 = mapOf(1 to "A", 10 to "B", 20 to "C")
    println(n1)
    val z1 = n1 + Pair(1, "Zinyan")
    println(z1)

    val z2 = z1 + Pair(2, "A2")
    println(z2)
        
    val z3 = z2+ mapOf("z" to 1,"x" to 2)
    println(z3)
}
//输出
{1=A, 10=B, 20=C}
{1=Zinyan, 10=B, 20=C}
{1=Zinyan, 10=B, 20=C, 2=A2}
{1=Zinyan, 10=B, 20=C, 2=A2, z=1, x=2}

结论:

1.map对象的操作左侧需要是一个Pari对象或者Map对象。而不能是随意的元素。

2.针对map的加减操作都是通过key进行判断的。如果key值相同,就会进行替换操作。(Map不允许Key值重复的元素存在。所以就会进行替换)

minus的操作,针对map来说,就是将mapkey独立成一个集合进行的操作了。

minus和普通集合的操作效果是一样的,减号后面不用添加Pari对象了。

示例:

fun main(string: Array<String>) {
    //我们定义了一个 key 是int,value 是字符的map 对象
    val n1 = mapOf(1 to "A", 10 to "B", 20 to "C")
    println(n1)
    //无效
    val z1 = n1 - Pair(1, "B")
    println(z1)
    //正确写法
    val z2 = z1 - 1
    println(z2)
        
    //还可以减去 集合对象
    val z3 = n1 - listOf(1,2,3,10)
    println(z3)
}
//输出
{1=A, 10=B, 20=C}
{1=A, 10=B, 20=C}
{10=B, 20=C}
{20=C}

只是减法就不用考虑是否有重复的key 了。因为map下key 唯一

分组: groupBy

在kotlin提供的针对集合元素进行分组操作:groupBy()该函数才使用lambda语法,并返回一个map对象。

每个MapKey都是一个lambda结果,Value就是一个List 集合对象。

示例:

fun main(string: Array<String>) {
    val s = listOf("china","beijing","wuhan","guangzhou","Changsha","wuhu")
    val n1 = s.groupBy {
        //按照首字母大写进行分组操作
        it.first().uppercase()
    }
    println(n1)
}
//输出
{C=[china, Changsha], B=[beijing], W=[wuhan, wuhu], G=[guangzhou]}

这只是简单的根据某些关键字,进行分组的结果。

例如通讯录的分组效果,就可以用这个方法快速的实现。

在使用key进行分组的是,同时我们还可以针对返回的value进行逻辑判断并修改值。

简单描述就是:在分组的时候,直接将值给进行转换了而不是使用原始集合中的数值。

示例:

package com.zinyan.general


fun main(string: Array<String>) {
    val s = listOf("china", "beijing", "wuhan", "guangzhou", "Changsha", "wuhu")
    //进行分组操作,定义Key 的lambda 是字符串的首字母,定义value 全部转为大写
    val n = s.groupBy(keySelector = { it.first() }, valueTransform = {
        it.uppercase()
    })
    println(n)
}
//输出
{c=[CHINA], b=[BEIJING], w=[WUHAN, WUHU], g=[GUANGZHOU], C=[CHANGSHA]}

我们还可以根据参数进行判断,并修改结果
例如。将china 转为zhongguo

示例:

package com.zinyan.general


fun main(string: Array<String>) {
    val s = listOf("china", "beijing", "wuhan", "guangzhou", "Changsha", "wuhu")
    //进行分组操作,定义Key 的lambda 是字符串的首字母,定义value 全部转为大写
    val n = s.groupBy(keySelector = { it.first() }, valueTransform = {
        if (it.equals("china")) {
            "zhongguo".uppercase()
        } else
            it.uppercase()
    })
    println(n)
}
//输出
{c=[ZHONGGUO], b=[BEIJING], w=[WUHAN, WUHU], g=[GUANGZHOU], C=[CHANGSHA]}

在valueTransform 之中处理的结果,并不影响keySelecter 的结果。因为已经先进行了分组,然后再修改了分组后的参数结果。

Grouping

如果我们在分组之后,要针对每个分组进行操作。可以使用grouping

它主要三种支持操作:

  • eachCount() : 计算每个分组的元素数量。
  • fold()reduce(): 对每个分组结果分别执行flod和reduce操作,作为一个单独的集合并返回结果。
  • aggregate(): 将给定操作应用于每个组中的所有元素并返回结果。 这是对 Grouping 执行任何操作的通用方法。当折叠或缩小不够时,可使用它来实现自定义操作。

eachCount 统计元素

将字符串首字母大小进行分组。分组之后执行eachCount进行统计每个分组的元素

示例:

fun main(string: Array<String>) {
    val s = listOf("china", "beijing", "wuhan", "guangzhou", "Changsha", "wuhu")
   	//将字符串首字母大小进行分组。分组之后执行eachCount进行统计每个分组的元素
    val n = s.groupingBy { it.first().uppercase() }.eachCount()
    println(n)
}
//输出
{C=2, B=1, W=2, G=1}

fold() 和reduce() 折叠集合

它们依次将所提供的操作应用于集合元素并返回累积的结果。 操作有两个参数:先前的累积值和集合元素。

在有关聚合操作的介绍中,我们会再次详细了解。

示例:

reduce

fun main(string: Array<String>) {
    val s = listOf("china", "beijing", "wuhan", "guangzhou", "Changsha", "wuhu","najin","nanchang","nantong")
    
    val n = s.groupingBy { it.first().uppercase() }.reduce { key, accumulator, element ->
       "$accumulator-$element"
    }
    println(n)
}
//输出
{C=china-Changsha, B=beijing, W=wuhan-wuhu, G=guangzhou, N=najin-nanchang-nantong}

我们将分组后的结果, 其中accumulator 是累积者,element是当前元素

我们分组后,将所有字符进行了拼接

示例:
fold

fun main(string: Array<String>) {
    val s = listOf("china", "beijing", "wuhan", "guangzhou", "Changsha", "wuhu","najin","nanchang","nantong")

    val n = s.groupingBy { it.first().uppercase() }.fold("w", operation = { accumulator, element ->
        "$accumulator-$element"
    })
    println(n)
}
//输出
{C=w-china-Changsha, B=w-beijing, W=w-wuhan-wuhu, G=w-guangzhou, N=w-najin-nanchang-nantong}

我们可以看到fold 和reduce 其实都是差不多的,针对数据进行折叠。

区别就是fold 可以添加一个参数,参与第一轮。而reduce 从本身元素的第一轮开始。

aggregate 扩展

如果统计和折叠都不满足需求,我们就需要使用aggregate进行扩展,自定义自己想实现的效果。

我们之后使用中,可能更多的将会使用aggregate,扩展我们针对分组后的数据的处理。

示例:

fun main(string: Array<String>) {
    //我们得到一个字符串数组
    val test = listOf(
        "beijin",
        "guangzhou",
        "wuhan",
        "shenzhen",
        "shanghai",
        "chendu",
        "chongqing",
        "nanjing",
        "changsha"
    )
    //按照首字母进行分组
    val ss = test.groupingBy { it.first() }.aggregate { key, accumulator: String?, element, first ->
        if (first) {
            //如果是第一项。 头部显示内容
            "开始:$element"
        } else {
            "$accumulator,$element"
        }
    }
    println(ss)
}
//输出
{b=开始:beijin, g=开始:guangzhou, w=开始:wuhan, s=开始:shenzhen,shanghai, c=开始:chendu,chongqing,changsha, n=开始:nanjing}

我们对照代码来进行理解,可以很方便我们明白

key :是 分组的组名,

accumulator :聚合计算得到后的结果. 我们可以自定义该数据类型。数据类型觉得了该方法返回的结果。

element: 当前元素值。也就是分组数据的单项结果。

我们如果翻一下源码,就可以知道

fold以及reduce都是基于aggregate进行的定制化方法。

源码:

@SinceKotlin("1.1")
public inline fun <T, K, R> Grouping<T, K>.fold(
    initialValueSelector: (key: K, element: T) -> R,
    operation: (key: K, accumulator: R, element: T) -> R
): Map<K, R> =
    @Suppress("UNCHECKED_CAST")
    aggregate { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) }

@SinceKotlin("1.1")
public inline fun <S, T : S, K> Grouping<T, K>.reduce(
    operation: (key: K, accumulator: S, element: T) -> S
): Map<K, S> =
    aggregate { key, acc, e, first ->
        @Suppress("UNCHECKED_CAST")
        if (first) e else operation(key, acc as S, e)
    }


@SinceKotlin("1.1")
public actual fun <T, K> Grouping<T, K>.eachCount(): Map<K, Int> =
// fold(0) { acc, e -> acc + 1 } optimized for boxing
    foldTo(destination = mutableMapOf(),
           initialValueSelector = { _, _ -> kotlin.jvm.internal.Ref.IntRef() },
           operation = { _, acc, _ -> acc.apply { element += 1 } })
        .mapValuesInPlace { it.value.element }

在补一份 官方讲解aggregated的示例

val numbers = listOf(3, 4, 5, 6, 7, 8, 9)

val aggregated = numbers.groupingBy { it % 3 }.aggregate { key, accumulator: StringBuilder?, element, first ->
    if (first) // first element
    StringBuilder().append(key).append(":").append(element)
    else
    accumulator!!.append("-").append(element)
}

println(aggregated.values) // [0:3-6-9, 1:4-7, 2:5-8]
2

评论区