如何使用集合排序(Collection Ordering)

如何使用集合排序(Collection Ordering)

集合內的排序是一個重要的概念,舉個例子來說,集合內如果有兩個元素不相等,它們排序的結果就會不一樣。

在 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