前言
本文是针对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:
代码语言:javascript复制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:
代码语言:javascript复制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 :
代码语言:javascript复制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 减号操作
示例:
代码语言:javascript复制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 操作之后得到的都是一个新的集合,原集合不会产生变化。
如果,集合减去的是另外一个集合,和减去的是一个元素。两种情况下计算方式有较大差别
示例:
代码语言:javascript复制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操作符。先通过示例代码来了解一下。
代码语言:javascript复制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
来说,就是将map
的key
独立成一个集合进行的操作了。
minus
和普通集合的操作效果是一样的,减号后面不用添加Pari
对象了。
示例:
代码语言:javascript复制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
对象。
每个Map
的Key
都是一个lambda
结果,Value
就是一个List
集合对象。
示例:
代码语言:javascript复制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进行逻辑判断并修改值。
简单描述就是:在分组的时候,直接将值给进行转换了而不是使用原始集合中的数值。
示例:
代码语言:javascript复制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
示例:
代码语言:javascript复制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进行统计每个分组的元素
示例:
代码语言:javascript复制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
代码语言:javascript复制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
代码语言:javascript复制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,扩展我们针对分组后的数据的处理。
示例:
代码语言:javascript复制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进行的定制化方法。
源码:
代码语言:javascript复制@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的示例
代码语言:javascript复制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]