Kotlin集合的操作(2)
九路 317 4

集合的 plus 与 minus 操作符

在 Kotlin 中,为集合定义了 plus (+)minus (-) 操作符。 它们把一个集合作为第一个操作数;第二个操作数可以是一个元素或者是另一个集合。 返回值是一个新的只读集合:

  • plus 的结果包含原始集合 和 第二个操作数中的元素。
  • minus 的结果包含原始集合中的元素,但第二个操作数中的元素 除外。 如果第二个操作数是一个元素,那么 minus 移除其在原始集合中的 第一次 出现;如果是一个集合,那么移除其元素在原始集合中的 所有 出现。
    val numbers = listOf("one", "two", "three", "four")
    

val plusList = numbers + "five" val minusList = numbers - listOf("three", "four") println(plusList) println(minusList)


有关 map 的 `plus` 和 `minus` 操作符的详细信息,请参见 Map 相关操作。 也为集合定义了广义赋值操作符 plusAssign (`+=`) 和 minusAssign (`-=`)。

 然而,对于只读集合,它们实际上使用 `plus` 或者 `minus` 操作符并尝试将结果赋值给同一变量。 因此,它们仅在由 var 声明的只读集合中可用。

 对于可变集合,如果它是一个 val,那么它们会修改集合。更多详细信息请参见集合写操作。

## 分组
Kotlin 标准库提供用于对集合元素进行分组的扩展函数。 基本函数 `groupBy()` 使用一个 lambda 函数并返回一个 `Map`。 
在此 `Map` 中,每个键都是 lambda 结果,而对应的值是返回此结果的元素 `List`。 
例如,可以使用此函数将 `String` 列表按首字母分组。

val numbers = listOf("one", "two", "three", "four", "five")

println(numbers.groupBy { it.first().toUpperCase() }) println(numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.toUpperCase() }))



如果要对元素进行分组,然后一次将操作应用于所有分组,请使用 groupingBy() 函数。 它返回一个 `Grouping` 类型的实例。 通过 Grouping 实例,可以以一种惰性的方式将操作应用于所有组:这些分组实际上是刚好在执行操作前构建的。


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

val numbers = listOf("one", "two", "three", "four", "five", "six") println(numbers.groupingBy { it.first() }.eachCount())


## 取集合的一部分
Kotlin 标准库包含用于取集合的一部分的扩展函数。 这些函数提供了多种方法来选择结果集合的元素:显式列出其位置、指定结果大小等。


#### Slice

`slice()` 返回具有给定索引的集合元素列表。 索引既可以是作为区间传入的也可以是作为整数值的集合传入的。

val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.slice(1..3)) println(numbers.slice(0..4 step 2)) println(numbers.slice(setOf(3, 5, 0)))



#### Take 与 drop
要从头开始获取指定数量的元素,请使用 `take()` 函数。 
要从尾开始获取指定数量的元素,请使用 `takeLast()`。 
当调用的数字大于集合的大小时,两个函数都将返回整个集合。

要从头或从尾去除给定数量的元素,请调用 drop() 或 dropLast() 函数。

val numbers = listOf("one", "two", "three", "four", "five", "six") println(numbers.take(3)) println(numbers.takeLast(3)) println(numbers.drop(1)) println(numbers.dropLast(5))


还可以使用谓词来定义要获取或去除的元素的数量。 有四个与上述功能相似的函数:
- `takeWhile()` 是带有谓词的 `take()`:它将不停获取元素直到排除与谓词匹配的首个元素。如果首个集合元素与谓词匹配,则结果为空。

- `takeLastWhile()` 与 `takeLast()` 类似:它从集合末尾获取与谓词匹配的元素区间。区间的首个元素是与谓词不匹配的最后一个元素右边的元素。如果最后一个集合元素与谓词匹配,则结果为空。

- `dropWhile()` 与具有相同谓词的 `takeWhile()` 相反:它将首个与谓词不匹配的元素返回到末尾。

- `dropLastWhile()` 与具有相同谓词的 `takeLastWhile()` 相反:它返回从开头到最后一个与谓词不匹配的元素。

val numbers = listOf("one", "two", "three", "four", "five", "six") println(numbers.takeWhile { !it.startsWith('f') }) println(numbers.takeLastWhile { it != "three" }) println(numbers.dropWhile { it.length == 3 }) println(numbers.dropLastWhile { it.contains('i') })



#### Chunked
要将集合分解为给定大小的“块”,请使用 `chunked()` 函数。 `chunked()` 采用一个参数(块的大小),并返回一个 `List` 其中包含给定大小的 `List`。
 第一个块从第一个元素开始并包含 size 元素,第二个块包含下一个 size 元素,依此类推。 最后一个块的大小可能较小。

val numbers = (0..13).toList() println(numbers.chunked(3))


还可以立即对返回的块应用转换。 为此,请在调用 `chunked()` 时将转换作为 lambda 函数提供。 lambda 参数是集合的一块。当通过转换调用 `chunked()` 时, 这些块是临时的 `List`,应立即在该 lambda 中使用。

val numbers = (0..13).toList() println(numbers.chunked(3) { it.sum() }) // it 为原始集合的一个块


#### Windowed
可以检索给定大小的集合元素中所有可能区间。 获取它们的函数称为 `windowed()`:它返回一个元素区间列表,比如通过给定大小的滑动窗口查看集合,则会看到该区间。
与 `chunked()` 不同,`windowed()` 返回从每个集合元素开始的元素区间(窗口)。 所有窗口都作为单个 `List` 的元素返回。

val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.windowed(3))


`windowed()` 通过可选参数提供更大的灵活性:

- `step` 定义两个相邻窗口的第一个元素之间的距离。默认情况下,该值为 1,因此结果包含从所有元素开始的窗口。如果将 step 增加到 2,将只收到以奇数元素开头的窗口:第一个、第三个等。

- `partialWindows` 包含从集合末尾的元素开始的较小的窗口。例如,如果请求三个元素的窗口,就不能为最后两个元素构建它们。在本例中,启用 `partialWindows` 将包括两个大小为2与1的列表。

val numbers = (1..10).toList() println(numbers.windowed(3, step = 2, partialWindows = true)) println(numbers.windowed(3) { it.sum() })


要构建两个元素的窗口,有一个单独的函数——zipWithNext()。 它创建接收器集合的相邻元素对。 请注意,zipWithNext() 不会将集合分成几对;它为 每个 元素创建除最后一个元素外的对,因此它在 [1, 2, 3, 4] 上的结果为 [[1, 2], [2, 3], [3, 4]],而不是 [[1, 2],[3, 4]]。 zipWithNext() 也可以通过转换函数来调用;它应该以接收者集合的两个元素作为参数。

val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.zipWithNext()) println(numbers.zipWithNext() { s1, s2 -> s1.length > s2.length})


## 取单个元素

Kotlin 集合提供了一套从集合中检索单个元素的函数。 此页面描述的函数适用于 list 和 set。

正如 list 的定义所言,list 是有序集合。 因此,list 中的每个元素都有其位置可供你引用。 除了此页面上描述的函数外,list 还提供了更广泛的一套方法去按索引检索和搜索元素。 有关更多详细信息,请参见 List 相关操作。


反过来,从定义来看,set 并不是有序集合。 但是,Kotlin 中的 Set 按某些顺序存储元素。 这些可以是插入顺序(在 `LinkedHashSet` 中)、自然排序顺序(在 `SortedSet` 中)或者其他顺序。 一组元素的顺序也可以是未知的。 在这种情况下,元素仍会以某种顺序排序,因此,依赖元素位置的函数仍会返回其结果。 但是,除非调用者知道所使用的 Set 的具体实现,否则这些结果对于调用者是不可预测的。


#### 按位置取
为了检索特定位置的元素,有一个函数 elementAt()。 用一个整数作为参数来调用它,你会得到给定位置的集合元素。 第一个元素的位置是 0,最后一个元素的位置是 (size - 1)。


`elementAt()` 对于不提供索引访问或非静态已知提供索引访问的集合很有用。 
在使用 `List` 的情况下,使用索引访问操作符 (`get()` 或 `[]`)更为习惯。

val numbers = linkedSetOf("one", "two", "three", "four", "five") println(numbers.elementAt(3))

val numbersSortedSet = sortedSetOf("one", "two", "three", "four") println(numbersSortedSet.elementAt(0)) // 元素以升序存储


还有一些有用的别名来检索集合的第一个和最后一个元素:`first()` 和 `last()`。

val numbers = listOf("one", "two", "three", "four", "five") println(numbers.first())
println(numbers.last())



为了避免在检索位置不存在的元素时出现异常,请使用 `elementAt()` 的安全变体:

- 当指定位置超出集合范围时,elementAtOrNull() 返回 null。

- elementAtOrElse() 还接受一个 lambda 表达式,该表达式能将一个 Int 参数映射为一个集合元素类型的实例。 当使用一个越界位置来调用时,elementAtOrElse() 返回对给定值调用该 lambda 表达式的结果。

val numbers = listOf("one", "two", "three", "four", "five") println(numbers.elementAtOrNull(5)) println(numbers.elementAtOrElse(5) { index -> "The value for index $index is undefined"})



#### 按条件取
函数 `first()` 和 `last()` 还可以让你在集合中搜索与给定谓词匹配的元素。 
当你使用测试集合元素的谓词调用 `first()` 时,你会得到对其调用谓词产生 `true` 的第一个元素。 反过来,带有一个谓词的 `last()` 返回与其匹配的最后一个元素。

val numbers = listOf("one", "two", "three", "four", "five", "six") println(numbers.first { it.length > 3 }) println(numbers.last { it.startsWith("f") })


如果没有元素与谓词匹配,两个函数都会抛异常。 为了避免它们,请改用 `firstOrNull()` 和 `lastOrNull()`:如果找不到匹配的元素,它们将返回 null。

val numbers = listOf("one", "two", "three", "four", "five", "six") println(numbers.firstOrNull { it.length > 6 })


或者,如果别名更适合你的情况,那么可以使用别名:
- 使用 `find()` 代替 `firstOrNull()`
- 使用 `findLast()` 代替 `lastOrNull()`

val numbers = listOf(1, 2, 3, 4) println(numbers.find { it % 2 == 0 }) println(numbers.findLast { it % 2 == 0 })


#### 随机取元素
如果需要检索集合的一个随机元素,那么请调用 `random()` 函数。 你可以不带参数或者使用一个 Random 对象作为随机源来调用它。

val numbers = listOf(1, 2, 3, 4) println(numbers.random())



#### 检测存在与否
如需检查集合中某个元素的存在,可以使用 `contains()` 函数。 如果存在一个集合元素等于(`equals()`)函数参数,那么它返回 `true`。
你可以使用 `in` 关键字以操作符的形式调用 `contains()`。


如需一次检查多个实例的存在,可以使用这些实例的集合作为参数调用 `containsAll()`。

val numbers = listOf("one", "two", "three", "four", "five", "six") println(numbers.contains("four")) println("zero" in numbers)

println(numbers.containsAll(listOf("four", "two"))) println(numbers.containsAll(listOf("one", "zero")))


此外,你可以通过调用 isEmpty() 和 isNotEmpty() 来检查集合中是否包含任何元素。

val numbers = listOf("one", "two", "three", "four", "five", "six") println(numbers.isEmpty()) println(numbers.isNotEmpty())

val empty = emptyList() println(empty.isEmpty()) println(empty.isNotEmpty())


#### 集合排序
元素的顺序是某些集合类型的一个重要方面。 例如,如果拥有相同元素的两个列表的元素顺序不同,那么这两个列表也不相等。

在 Kotlin 中,可以通过多种方式定义对象的顺序。

首先,有 自然 顺序。它是为 `Comparable` 接口的继承者定义的。 当没有指定其他顺序时,使用自然顺序为它们排序。


大多数内置类型是可比较的:
- 数值类型使用传统的数值顺序:1 大于 0; -3.4f 大于 -5f,以此类推。
- `Char` 和 `String` 使用字典顺序: b 大于 a; world 大于 hello。

如需为用户定义的类型定义一个自然顺序,可以让这个类型继承 `Comparable`。 这需要实现 `compareTo()`函数。 `compareTo()` 必须将另一个具有相同类型的对象作为参数并返回一个整数值来显示哪个对象更大:

- 正值表明接收者对象更大。
- 负值表明它小于参数。
- 0 说明对象相等。

下面是一个类,可用于排序由主版本号和次版本号两部分组成的版本。

class Version(val major: Int, val minor: Int): Comparable { override fun compareTo(other: Version): Int { if (this.major != other.major) { return this.major - other.major } else if (this.minor != other.minor) { return this.minor - other.minor } else return 0 } }

fun main() {
println(Version(1, 2) > Version(1, 3)) println(Version(2, 0) > Version(1, 5)) }

自定义 顺序让你可以按自己喜欢的方式对任何类型的实例进行排序。 特别是,你可以为不可比较类型定义顺序,或者为可比较类型定义非自然顺序。 如需为类型定义自定义顺序,可以为其创建一个 Comparator。 `Comparator` 包含 `compare()` 函数:它接受一个类的两个实例并返回它们之间比较的整数结果。 如上所述,对结果的解释与 `compareTo() `的结果相同。

val lengthComparator = Comparator { str1: String, str2: String -> str1.length - str2.length } println(listOf("aaa", "bb", "c").sortedWith(lengthComparator))

有了 `lengthComparator`,你可以按照字符串的长度而不是默认的字典顺序来排列字符串。

定义一个 `Comparator` 的一种比较简短的方式是标准库中的`compareBy()`函数。 `compareBy()`接受一个 lambda 表达式,该表达式从一个实例产生一个 `Comparable` 值,并将自定义顺序定义为生成值的自然顺序。 使用 `compareBy()`,上面示例中的长度比较器如下所示:

println(listOf("aaa", "bb", "c").sortedWith(compareBy { it.length }))

Kotlin 集合包提供了用于按照自然顺序、自定义顺序甚至随机顺序对集合排序的函数。 在此页面上,我们将介绍适用于只读集合的排序函数。 这些函数将它们的结果作为一个新集合返回,集合里包含了按照请求顺序排序的来自原始集合的元素。 如果想学习就地对可变集合排序的函数,请参见 List 相关操作。

#### 自然顺序
基本的函数 sorted() 和 sortedDescending() 返回集合的元素,这些元素按照其自然顺序升序和降序排序。 这些函数适用于 `Comparable`元素的集合。

val numbers = listOf("one", "two", "three", "four")

println("Sorted ascending: ${numbers.sorted()}") println("Sorted descending: ${numbers.sortedDescending()}")

#### 自定义顺序
为了按照自定义顺序排序或者对不可比较对象排序,可以使用函数 sortedBy() 和 sortedByDescending()。 它们接受一个将集合元素映射为 `Comparable`值的选择器函数,并以该值的自然顺序对集合排序。

val numbers = listOf("one", "two", "three", "four")

val sortedNumbers = numbers.sortedBy { it.length } println("Sorted by length ascending: $sortedNumbers") val sortedByLast = numbers.sortedByDescending { it.last() } println("Sorted by the last letter descending: $sortedByLast")

如需为集合排序定义自定义顺序,可以提供自己的 `Comparator`。 为此,调用传入 `Comparator` 的 sortedWith() 函数。 使用此函数,按照字符串长度排序如下所示:

val numbers = listOf("one", "two", "three", "four") println("Sorted by length ascending: ${numbers.sortedWith(compareBy { it.length })}")

#### 倒序
你可以使用 reversed() 函数以相反的顺序检索集合。

val numbers = listOf("one", "two", "three", "four") println(numbers.reversed())

`reversed()` 返回带有元素副本的新集合。 因此,如果你之后改变了原始集合,这并不会影响先前获得的 `reversed()` 的结果。

另一个反向函数——asReversed()——返回相同集合实例的一个反向视图,因此,如果原始列表不会发生变化,那么它会比 `reversed()` 更轻量,更合适。

val numbers = listOf("one", "two", "three", "four") val reversedNumbers = numbers.asReversed() println(reversedNumbers) val numbers = listOf("one", "two", "three", "four") val reversedNumbers = numbers.asReversed() println(reversedNumbers)

如果原始列表是可变的,那么其所有更改都会反映在其反向视图中,反之亦然。

val numbers = mutableListOf("one", "two", "three", "four") val reversedNumbers = numbers.asReversed() println(reversedNumbers) numbers.add("five") println(reversedNumbers)

但是,如果列表的可变性未知或者源根本不是一个列表,那么`reversed()` 更合适,因为其结果是一个未来不会更改的副本。

随机顺序
最后,shuffled() 函数返回一个包含了以随机顺序排序的集合元素的新的 `List`。 你可以不带参数或者使用 Random 对象来调用它。

val numbers = listOf("one", "two", "three", "four") println(numbers.shuffled())

`````` language ```

评论区

索引目录