前言
本篇将学习如何从集合中查询元素find, 随机获取元素random。检测集合元素是否存在,集合是否为null。
比较两个集合是否包含,以及集合的各种截取方式。
引读
这是与集合相关的其他三篇文章。列出来,方便进行查处和跳转。
Kotlin 集合 基本介绍 - Z同学 (zinyan.com)
Kotlin 集合-转换,过滤和检测 - Z同学 (zinyan.com)
Kotlin集合-plus,minus和分组group详解 - Z同学 (zinyan.com)
查询
我们在获取集合的元素时,除了遍历获取,和指定下标位置进行获取外,还有更多的查询获取方式。
indexOf() 和lastIndexOf() 查找元素下标
在列表中,都可以使用indexOf()
或者lastIndexOf()
查询到元素在集合中的位置。
如果集合中没有该对象,则返回-1
。
fun main(string: Array<String>) {
val numbers = listOf(1, 2, 3, 4, 2, 5)
println(numbers.indexOf(2))
println(numbers.lastIndexOf(2))
}
//输出
1
4
还有一种扩展方法
indexOfFirst()
返回与关键字匹配的第一个元素的索引,如果没有此类元素,则返回-1
。indexOfLast()
返回与谓词匹配的最后一个元素的索引,如果没有此类元素,则返回-1
。
indexOfFirst() 和indexOfLast()
相关说明已经在上面介绍了。下面主要是提供一个示例代码。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val numbers = mutableListOf("A", "B", "B", "C", "C", "A", "C")
println(numbers.indexOfFirst { it == "A" })
println(numbers.indexOfLast { it == "A" })
}
//输出
0
5
elementAt() 获取元素
指定下标获取元素。
所以我们使用时要注意下标越界。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG", "HH")
println(test.elementAt(2))
}
//输出
CC
我们如果输入的下标超过了集合的长度,会提示ArrayIndexOutOfBoundsException错误。
那么有没有安全写法呢?有 :elementAtOrNull()和elementAtOrElse()。
使用elementAtOrNull 时,当下标越界时返回null。
如果使用elementAtOrElse,需要自己定义一个lambda表达式,如果出现越界时就会返回该表达式的结果。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("AA", "BB", "CC",)
println(test.elementAtOrNull(100))
//没越界
println(test.elementAtOrElse(2,defaultValue = {
"$it 这个下标不存在 "
}))
//越界后
println(test.elementAtOrElse(22,defaultValue = {
"$it 这个下标不存在 "
}))
}
//输出
null
CC
22 这个下标不存在
first() 和 last()
获取集合的首尾元素。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("AA", "BB", "CC",)
println(test.first())
println(test.last())
}
//输出
AA
CC
这是通常默认的写法,我们还可以针对该函数进行扩展定义进行查询。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
//从开头查询,字符长度大3的元素
println(test.first {
it.length > 3
})
//从结尾查询,字符长度大于3的元素
println(test.last {
it.length > 3
})
}
//输出
three
five
但是我们如果所有字符长度都不满足的时候,也就是说没有元素满足查询条件时,会出现
NoSuchElementException: Collection contains no element matching the predicate.错误
如果要避免异常的产生,就需要使用他们的安全写法 firstOrNull()
和 lastOrNull()
写法是一样的,如果没有查询到,就会返回null对象。
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
//从开头查询,字符长度大3的元素
println(test.firstOrNull {
it.length > 10
})
}
//输出
null
find() 和 findLast()
其实效果和first,last是一样的,也进行查询遍历。但是他们不会出现异常。通常情况下,建议使用find和findLast 进行查询
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
//从开头查询,字符长度大3的元素
println(test.find {
it.length > 2
})
println(test.findLast { it.length > 3 })
}
//输出
one
five
random()随机
我们如果需要随机从数组集合中抓取元素,那么就使用random函数。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
//我们遍历三遍,随机获取元素
for (i in 1..3) {
println(test.random())
}
}
//输出
four
three
six
将能够随机从集合中得到数据,而且不会出现越界问题。
binarySearch() 结果查询
该方法是一种二分查找,但是是针对排序后的元素进行查找。
代码语言:javascript复制fun main(string: Array<String>) {
val numbers = mutableListOf("one", "two", "three", "four")
numbers.sort()
println(numbers)
println(numbers.binarySearch("two")) // 3
println(numbers.binarySearch("z")) // -5
//指定搜索索引区间:将会在两个索引范围内进行查询
println(numbers.binarySearch("two", 0, 2)) // -3
}
//输出
[four, one, three, two]
3
-5
-3
如果存在这样的元素,则函数返回其索引;否则,将返回 (-insertionPoint - 1)
,其中 insertionPoint
为应插入此元素的索引,以便列表保持排序。如果有多个具有给定值的元素,搜索则可以返回其任何索引。
检测
我们使用集合时,往往会需要检测集合中某个元素的存在。kotlin提供了相应的函数。
contains()
如果存在一个集合元素等于(equals()
)函数参数,那么它返回 true
。
支持单对多,也支持多对多的比较。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
//检测元素是否存在
println(test.contains("three1"))
println(test.contains("three"))
}
//输出
false
true
我们同时也可以使用in
关键字,用操作符形式调用 contains()
。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
//我们遍历三遍,随机获取元素
println("three" in test)
}
//输出
true
我们如果要比较一个集合是否包含另外一个集合的全部元素
可以使用containsAll()
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
// 同时,支持多对多的比较
println(test.containsAll(listOf("A","six")))
println(test.containsAll(listOf("four","two")))
}
//输出
false
true
isEmpty 和isNotEmpty 判空
我们除了判断集合是否有元素,我们有时候还需要先判断,集合是否为null。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
val test = listOf("one", "two", "three", "four", "five", "six")
println(test.isEmpty())
println(test.isNotEmpty())
}
//输出
false
true
isEmpty:集合是否为null,如果为null 返回true,不为空返回false
isNotEmpty:集合是否为null,如果为null 返回 false,不为空返回true
两个方法的判断刚好是相反的
截取
Kotlin针对集合的参数提取,提供了一组定义好的扩展函数。方便我们便捷的从集合中获取想要的数据。
截取后的值将会存储在一个新的集合数组中。不会修改原数据集合。
Slice()切片
该函数返回具有给定索引的集合元素列表。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("A", "B", "C", "D", "E", "F", "G")
//截取 下标从2 到4的 集合集
val temp = test.slice(listOf(1,4,3))
println(temp)
}
//输出
[B, E, D]
我们注意到,截取后的集合,也是按照我们传入的下标顺序进行获取的。
索引既可以是作为集合的形式传入,也可以是[区间](Kotlin -区间数列详解 - Z同学 (zinyan.com))传入。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("A", "B", "C", "D", "E", "F", "G")
//截取 下标从2 到4的 集合集
val temp = test.slice(2..4)
println(temp)
//截取 步长为2的 指定区间的参数
val temp1 = test.slice(2..4 step 2)
println(temp1)
}
//输出
[C, D, E]
[C, E]
我们乘此机会,也刚好温习一下关于区间的方法。
如果我们定义的索引超过了集合会怎么样呢?
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("A", "B", "C", "D", "E", "F", "G")
//截取 下标从2 到9的 集合集
val temp = test.slice(2..9)
println(temp)
}
//输出
Exception in thread "main" java.lang.IndexOutOfBoundsException: toIndex = 10
at java.util.SubList.<init>(AbstractList.java:622)
at java.util.RandomAccessSubList.<init>(AbstractList.java:775)
at java.util.AbstractList.subList(AbstractList.java:484)
at kotlin.collections.CollectionsKt___CollectionsKt.slice(_Collections.kt:864)
at com.zinyan.general.ListTempKt.main(ListTemp.kt:8)
结果,当日就是下标越界了。
所以使用Slice要注意越界的问题。
其次,Slice是根据索引进行截取的。所以Map 是没有Slice函数的。
Take() 和 drop() 获取指定长度
我们使用Slice 其实可以实现take 和drop的效果。而且slice 还可以任意截取。
唯一问题就是slice会越界。
而take 和drop截取的时候不会产生越界的问题。
- take :从头开始获取指定长度的元素,如果原始集合长度不够,则返回整个集合对象。
- drop:从指引位置开始截取到集合尾部。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("A", "B", "C", "D", "E", "F", "G")
//从头截取
val temp = test.take(5)
println(temp)
//从下标2的元素开始, 包括下标2的元素本身
val temp1 = test.drop(2)
println(temp1)
}
//输出
[A, B, C, D, E]
[C, D, E, F, G]
和这两个基本函数类似的还有几个扩展函数。满足我们不同的截取需求。
例如倒着截取,截取匹配字段的元素,截取不匹配字段的元素等等。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("A", "B", "C", "D", "E", "F", "G")
//倒叙从最后开始截取三个元素
println("从尾部开始截取:${test.takeLast(3)}")
//排除后面3位元素后,进行截取
println("从尾部开始截取:${test.dropLast(3)}")
}
//输出
从尾部开始截取:[E, F, G]
从尾部开始截取:[A, B, C, D]
截取指定的参数
takeWhile 和 takeLastWhile
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG")
//从左往右开始截取, 字符串首字母A开始 到不满足条件就截止
println("截取:${test.takeWhile { it.startsWith('A') }}")
//开始位置不是C ,如果开始第一个元素不匹配,直接返回空集合
println("截取:${test.takeWhile { it.startsWith('C') }}")
//从右往左开始截取, 字符串首字母A的。如果最后一个元素不匹配,直接返回空集合
println("从后截取:${test.takeLastWhile { it.startsWith('A') }}")
println("从后截取:${test.takeLastWhile { it.startsWith('G') }}")
}
//输出
截取:[AA, AB]
截取:[]
从后截取:[]
从后截取:[GF, GG]
总结:
takeWhile:从左往右开始截取,只有满足lambda方法的元素被截取。直到碰见不匹配的元素结束截取。
如果左边第一个元素就不匹配,则直接返回空集合对象。
takeLastWhile:从右往左开始截取,只有满足lambda方法的元素被截取。直到碰见不匹配的元素结束截取。
如果右边第一个元素就不匹配,则直接返回空集合对象。截取后的顺序还是保持从左到右的顺序。
dropWhile 和 dropLastWhile
和takeWhile
的截取逻辑刚好相反。单纯描述很难理解,我们通过示例可以直观比较。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG")
//从左往右开始截取, 满足条件的就不进行截取
println("截取:${test.dropWhile { it.startsWith('A') }}")
println("截取:${test.dropWhile { it.startsWith('C') }}")
println("从后截取:${test.dropLastWhile { it.startsWith('A') }}")
println("从后截取:${test.dropLastWhile { it.startsWith('G') }}")
}
//输出
截取:[CC, DD, EE, GF, GG]
截取:[AA, AB, CC, DD, EE, GF, GG]
从后截取:[AA, AB, CC, DD, EE, GF, GG]
从后截取:[AA, AB, CC, DD, EE]
总结:
**dropWhile **:从左往右开始截取,从第一个不匹配Lambda等式的元素开始,截取到数组最后一个元素
dropLastWhile:从右往左开始截取,从右边第一个元素开始,从不匹配Lambda等式的元素开始,截取到最后一个元素。
drop截取时,如果中间碰见满足等式的元素也没有关系。也会进行截取。
Chunked() 区块
主要是将集合分解为指定大小的一个一个的区块。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG")
//将集合拆分指定的大小的块 例如 长度3
println(test.chunked(3))
}
//输出:
[[AA, AB, CC], [DD, EE, GF], [GG]]
我们还可以在切块的时候,针对块结果进行其他的转换。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG")
//将集合拆分指定的大小的块 例如 长度3
println(test.chunked(3) { it.groupBy { v -> v.first() } })
}
//输出
[{A=[AA, AB], C=[CC]}, {D=[DD], E=[EE], G=[GF]}, {G=[GG]}]
针对块数据,进行分组操作。
Windowed() 视窗化
截取的效果可能和Chunked很像,但是windowed 是按照区间的步骤进行截取
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG")
// 将元素按照指定宽带,列出所有的组成方法
println(test.windowed(3))
}
//输出
[[AA, AB, CC], [AB, CC, DD], [CC, DD, EE], [DD, EE, GF], [EE, GF, GG]]
类似:我们创建了一个3个元素宽的窗口,然后窗口在集合中按照默认步长1进行滑动。
每次滑动的结果,输出为List。结果就是我们上面的效果了。
我们可以通过windowed的可选参数进行调节:
step
定义窗口的滑动距离。默认情况下该值为 1,因此结果包含从所有元素开始的窗口。如果将 step 增加到 2,将只收到以奇数元素开头的窗口:第一个、第三个等。partialWindows
包含从集合末尾的元素开始的较小的窗口。例如,如果请求三个元素的窗口,就不能为最后两个元素构建它们。
根据示例,我们来理解上面的描述
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG","HH")
// 将元素按照指定宽带,列出所有的组成方法
println(test.windowed(3,step = 2))
}
//输出
[[AA, AB, CC], [CC, DD, EE], [EE, GF, GG]]
我们定义窗口步长为2之后,发现最后一个 GG 和HH 不满足窗口长度。
但是如果我们也想打印该怎么办?
使用partialWindows.
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG","HH")
// 将元素按照指定宽带,列出所有的组成方法
println(test.windowed(3,step = 2,partialWindows = true))
}
//输出
[[AA, AB, CC], [CC, DD, EE], [EE, GF, GG], [GG, HH]]
zipWithNext()
上面截取后,都是将结果转为了集合,zipWithNext 将集合截取成了Pair对象。
示例:
代码语言:javascript复制fun main(string: Array<String>) {
//我们得到一个字符串数组
val test = listOf("AA", "AB", "CC", "DD", "EE", "GF", "GG", "HH")
// 将元素按照指定宽带,列出所有的组成方法
val ss = test.zipWithNext()
println(ss)
println(ss[0].first)
println(ss[0].second)
}
//输出
[(AA, AB), (AB, CC), (CC, DD), (DD, EE), (EE, GF), (GF, GG), (GG, HH)]
AA
AB