集合內的排序是一個重要的概念,舉個例子來說,集合內如果有兩個元素不相等,它們排序的結果就會不一樣。
在 Kotlin 提供了許多可以讓你自己定義排序的方法,如果你是透過 Comparable
的方式進行排序,如果沒有特別定義,它就會採取自然排序的方式來進行排序。
- 什麼是自然排序?
舉個例子來說,如果你是數字,那麼根據常規 1 就比 0 大,-1 比 -2 大,如果英文字元則會採取字典序,就是 a, b, c, …, z 這樣的排序,就是 a 大於 b, Hi 大於 Zoo。
說明
你也可以採取自定義的排序。
class Version(val major: Int, val minor: Int): Comparable<Version> {
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))
}
結果如下。
false
true
實作 Comparable 介面,就必須覆寫 compareTo 這個方法,它必須回傳一個數值,一般來說,我們會將該類別物件跟傳進來的物件進行比較,規則如下。
回傳正值代表傳入物件比較大
回傳負值代表傳入物件比較小
回傳 0 代表兩物件相等
根據這樣的規則,就可以對於一個集合進行自定義的排序了。
如果你要自訂一個排序的話,可以透過 Comparator 來進行定義,透過一個 lambda 傳入兩個相比較的物件,對物件進行排序的定義,最後透過集合的 sortedWith 方法來處理自定義的排序方法。
val lengthComparator = Comparator { str1: String, str2: String -> str1.length - str2.length }
println(listOf("aaa", "bb", "c").sortedWith(lengthComparator))
Natural order
sorted()
和 sortedDescending()
是兩個基本排序方法,分別為升冪跟降冪,如果你將一個集合進行排序,就很容易理解這兩個的差異。
val numbers = listOf(1, 2, 3, 4)
println("Sorted ascending: ${numbers.sorted()}")
println("Sorted descending: ${numbers.sortedDescending()}")
結果如下。
Sorted ascending: [1, 2, 3, 4]
Sorted descending: [4, 3, 2, 1]
你會發現預設的 sorted 方法就是升冪排序,如果你用字串排序,就會如同前面所說的是以自然排序為規則。
Custom orders
如果你要客製化自己的排序,又想透過 lambda,你可以照下面程式碼這樣處理。
val numbers = listOf(1,2,3,4,5)
val sortedNumbers = numbers.sortedBy { it }
println("Sorted by ascending: $sortedNumbers")
val sortedByLast = numbers.sortedByDescending { it }
println("Sorted by descending: $sortedByLast")
結果如下。
Sorted by ascending: [1, 2, 3, 4, 5]
Sorted by descending: [5, 4, 3, 2, 1]
你也可以如同前面一開始提的方法,透過 sortedWith 來進行排序。
val numbers = listOf(1, 2, 3, 4, 5)
println("Sorted ascending: ${numbers.sortedWith(compareBy { it })}")
結果如下。
[1, 2, 3, 4, 5, 6]
還是不了解?那取 2 個元素試看看。
Sorted ascending: [1, 2, 3, 4, 5]
Reverse order
如果你要反轉排序,可以透過 reversed 這個方法。
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.reversed())
結果如下。
[5, 4, 3, 2, 1]
這邊還有一個方法是 asReversed,用法跟結果都跟 reversed 沒有什麼不一樣,但是文件上面表示如下。
returns a reversed view of the same collection instance, so it may be more lightweight and preferable than
reversed()
if the original list is not going to change.
也就是說它是一個輕量級的反轉,透過觀察原始碼,可以看到 reversed 的程式碼如下。
/**
* Returns a list with elements in reversed order.
*/
public fun <T> Iterable<T>.reversed(): List<T> {
if (this is Collection && size <= 1) return toList()
val list = toMutableList()
list.reverse()
return list
}
將其倒入到一個 MutableList 裡面,所以會多複製一個集合出來。
再看看 這邊的原始碼註解寫著。
Returns a reversed read-only view of the original List.
所以可以理解這邊講的輕量級應該是指空間上的節省。
Random order
如果要隨機排序,可以透過 shuffled 這個方法。
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.shuffled())
每次結果都不一樣,結果如下。
[5, 2, 1, 3, 4]
參考資料
https://kotlinlang.org/docs/reference/collection-ordering.html