9.1.1.4 排序
原文链接: https://kotlinlang.org/docs/collection-ordering.html
9.1.1.4 排序
元素的顺序是某些集合类型的重要方面。例如,包含相同元素但顺序不同的两个列表并不相等。
在 Kotlin 中,可以用多种方式定义对象的顺序。
首先是自然顺序。它是为 Comparable 接口的实现定义的。当没有指定其他顺序时,就用自然顺序对它们排序。
大多数内置类型都是可比较的:
- 数值类型使用传统的数值顺序:
1 大于 0;-3.4f 大于 -5f,依此类推。 Char 和 String 使用字典序:b 大于 a;world 大于 hello。
要为自定义类型定义自然顺序,请让该类型实现 Comparable。这需要实现 compareTo() 函数。compareTo() 必须接收集合同样类型的另一个对象作为实参,并返回一个表示哪个对象更大的整数值:
- 正值表示接收者对象更大。
- 负值表示它小于该实参。
- 零表示两者相等。
下面是一个用于对由主版本号和次版本号组成的版本进行比较的类。
1
2
3
4
5
6
7
8
9
10
11
12
| class Version(val major: Int, val minor: Int): Comparable<Version> {
override fun compareTo(other: Version): Int = when {
this.major != other.major -> this.major compareTo other.major // 中缀形式的 compareTo()
this.minor != other.minor -> this.minor compareTo other.minor
else -> 0
}
}
fun main() {
println(Version(1, 2) > Version(1, 3))
println(Version(2, 0) > Version(1, 5))
}
|
自定义顺序让你可以按自己喜欢的方式对任意类型的实例排序。特别是,你可以为非可比较对象定义顺序,或者为可比较类型定义不同于自然顺序的顺序。要为某个类型定义自定义顺序,请为它创建 Comparator。Comparator 包含 compare() 函数:它接收某个类的两个实例,并返回两者比较的整数结果。结果的解释方式与上面描述的 compareTo() 结果相同。
1
2
3
4
| fun main() {
val lengthComparator = Comparator { str1: String, str2: String -> str1.length - str2.length }
println(listOf("aaa", "bb", "c").sortedWith(lengthComparator))
}
|
有了 lengthComparator,你就能按字符串长度而不是默认的字典序来排列它们。
定义 Comparator 的更简短方式是使用标准库中的 compareBy() 函数。compareBy() 接收一个从实例产生 Comparable 值的 lambda 函数,并把自定义顺序定义为这些值的自然顺序。
使用 compareBy(),上面例子中的长度比较器可以写成这样:
1
2
3
| fun main() {
println(listOf("aaa", "bb", "c").sortedWith(compareBy { it.length }))
}
|
你也可以基于多个条件定义顺序。例如,要按字符串长度排序、长度相同时按字母顺序排序,你可以这样写:
1
2
3
4
5
6
7
8
9
10
11
12
| fun main() {
val sortedStrings = listOf("aaa", "bb", "c", "b", "a", "aa", "ccc")
.sortedWith { a, b ->
when (val compareLengths = a.length.compareTo(b.length)) {
0 -> a.compareTo(b)
else -> compareLengths
}
}
println(sortedStrings)
// [a, b, c, aa, bb, aaa, ccc]
}
|
由于按多个条件排序是常见场景,Kotlin 标准库提供了 .thenBy() 函数,用于添加次级排序规则。
例如,你可以把 compareBy() 与 .thenBy() 结合,先按长度、再按字母顺序对字符串排序,与上一个例子完全一样:
1
2
3
4
5
6
7
| fun main() {
val sortedStrings = listOf("aaa", "bb", "c", "b", "a", "aa", "ccc")
.sortedWith(compareBy<String> { it.length }.thenBy { it })
println(sortedStrings)
// [a, b, c, aa, bb, aaa, ccc]
}
|
Kotlin 集合包提供了按自然顺序、自定义顺序甚至随机顺序对集合排序的函数。本页将介绍适用于只读集合的排序函数。这些函数会把结果作为新集合返回,其中包含按所需顺序排列的原集合元素。要了解用于就地排序可变集合的函数,请参阅 List 特有操作。
自然顺序
基本函数 .sorted() 和 .sortedDescending() 按自然顺序把集合元素以升序和降序返回。这些函数适用于由 Comparable 元素组成的集合。
1
2
3
4
5
6
| fun main() {
val numbers = listOf("one", "two", "three", "four")
println("Sorted ascending: ${numbers.sorted()}")
println("Sorted descending: ${numbers.sortedDescending()}")
}
|
自定义顺序
要按自定义顺序排序或对不可比较的对象排序,可以使用 .sortedBy() 和 .sortedByDescending() 函数。它们接收一个选择器函数,把集合元素映射为 Comparable 值,并按这些值的自然顺序对集合排序。
1
2
3
4
5
6
7
8
| fun main() {
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。为此,请调用 .sortedWith() 扩展函数并传入你的 Comparator。用这个函数按长度对字符串排序看起来是这样的:
1
2
3
4
| fun main() {
val numbers = listOf("one", "two", "three", "four")
println("Sorted by length ascending: ${numbers.sortedWith(compareBy { it.length })}")
}
|
检查排序顺序
你可以使用以下扩展函数检查元素是否已经符合指定顺序:
.isSorted().isSortedDescending().isSortedWith(comparator).isSortedBy(selector).isSortedByDescending(selector)
如果元素符合指定顺序,或者元素少于两个,这些扩展函数返回 true。一旦发现不满足顺序的一对元素,它们就返回 false 并停止检查。
对于迭代顺序没有保证的集合(例如 HashSet),结果可能在不同调用之间变化。对于不以一致顺序产生元素的序列也是如此。要在多次调用中得到相同结果,请只在迭代顺序有保证的集合(例如 List)上使用这些函数。
在检查 Double 和 Float 值时,这些函数把 NaN 视为大于任何其他值,把 -0.0 视为小于 0.0。此外,.isSortedBy() 和 .isSortedByDescending() 函数把选择器结果为 null 的情况视为小于任何非空值。
在序列上调用这些函数时,该操作是终止操作。它会消费该序列以产生一个 Boolean 值,而不是返回另一个序列。
注意: 这些排序顺序检查函数也可用于数组、基本类型数组和无符号数组。无符号数组及其操作是实验性的,需要使用 @ExperimentalUnsignedTypes 注解选择启用。
下面是使用 .isSorted() 和 .isSortedBy() 函数检查排序顺序的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| data class User(val name: String, val age: Int)
fun main() {
val numbers = listOf(1, 2, 3, 4)
println(numbers.isSorted())
// true
val users = listOf(
User("Alice", 24),
User("Bob", 31),
User("Charlie", 29),
)
println(users.isSortedBy(User::age))
// false
val descending = listOf(4, 3, 2, 1)
println(descending.isSortedDescending())
// true
}
|
逆序
你可以使用 .reversed() 函数以逆序获取集合。
1
2
3
4
| fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers.reversed())
}
|
.reversed() 扩展函数返回一个包含元素副本的新集合。因此,如果你之后修改原集合,不会影响此前获得的 .reversed() 结果。
另一个逆序函数——.asReversed()
- 返回同一个集合实例的逆序视图,因此如果原列表不会发生变化,它可能比
.reversed() 更轻量、更可取。
1
2
3
4
5
| fun main() {
val numbers = listOf("one", "two", "three", "four")
val reversedNumbers = numbers.asReversed()
println(reversedNumbers)
}
|
如果原列表是可变的,它的所有改动都会反映到其逆序视图中,反之亦然。
1
2
3
4
5
6
7
| fun main() {
val numbers = mutableListOf("one", "two", "three", "four")
val reversedNumbers = numbers.asReversed()
println(reversedNumbers)
numbers.add("five")
println(reversedNumbers)
}
|
不过,如果列表的可变性未知,或者源根本不是列表,那么 .reversed() 更可取,因为它的结果是一份将来不会改变的副本。
随机顺序
最后,还有一个函数返回包含集合元素、顺序随机的新 List——.shuffled()。你可以在不带参数的情况下调用它,也可以传入一个 Random 对象。
1
2
3
4
| fun main() {
val numbers = listOf("one", "two", "three", "four")
println(numbers.shuffled())
}
|