Kotlin 集合 查询,检测,截取等方法介绍

2023-07-13 16:08:56 浏览数 (1)

前言

本篇将学习如何从集合中查询元素find, 随机获取元素random。检测集合元素是否存在,集合是否为null。

比较两个集合是否包含,以及集合的各种截取方式。

引读

这是与集合相关的其他三篇文章。列出来,方便进行查处和跳转。

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

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

Kotlin集合-plus,minus和分组group详解 - Z同学 (zinyan.com)

查询

我们在获取集合的元素时,除了遍历获取,和指定下标位置进行获取外,还有更多的查询获取方式。

indexOf() 和lastIndexOf() 查找元素下标

在列表中,都可以使用indexOf() 或者lastIndexOf() 查询到元素在集合中的位置。

如果集合中没有该对象,则返回-1

代码语言:javascript复制
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

0 人点赞