Làm thế nào để sắp xếp dựa trên / so sánh nhiều giá trị trong Kotlin?


84

Giả sử tôi có một class Foo(val a: String, val b: Int, val c: Date)và tôi muốn sắp xếp danh sách các Foos dựa trên cả ba thuộc tính. Làm thế nào tôi sẽ đi về điều này?

Câu trả lời:


145

Kotlin's stdlib cung cấp một số phương pháp trợ giúp hữu ích cho việc này.

Đầu tiên, bạn có thể xác định một bộ so sánh bằng cách sử dụng compareBy()phương thức và chuyển nó vào sortedWith()phương thức mở rộng để nhận một bản sao danh sách được sắp xếp:

val list: List<Foo> = ...
val sortedList = list.sortedWith(compareBy({ it.a }, { it.b }, { it.c }))

Thứ hai, bạn có thể cho phép Footriển khai Comparable<Foo>bằng cách sử dụng compareValuesBy()phương thức trợ giúp:

class Foo(val a: String, val b: Int, val c: Date) : Comparable<Foo> {
    override fun compareTo(other: Foo)
            = compareValuesBy(this, other, { it.a }, { it.b }, { it.c })
}

Sau đó, bạn có thể gọi sorted()phương thức mở rộng không có tham số để nhận một bản sao danh sách đã được sắp xếp:

val sortedList = list.sorted()

Hướng sắp xếp

Nếu bạn cần sắp xếp tăng dần trên một số giá trị và giảm dần trên các giá trị khác, stdlib cũng cung cấp các hàm cho điều đó:

list.sortedWith(compareBy<Foo> { it.a }.thenByDescending { it.b }.thenBy { it.c })

Cân nhắc về hiệu suất

Các varargphiên bản của compareValuesBykhông inlined trong bytecode nghĩa lớp nặc danh sẽ được tạo ra cho các lambdas. Tuy nhiên, nếu bản thân các lambdas không nắm bắt trạng thái, các thể hiện singleton sẽ được sử dụng thay vì khởi tạo lambdas mọi lúc.

Như Paul Woitaschek đã lưu ý trong các nhận xét, so sánh với nhiều bộ chọn sẽ khởi tạo một mảng cho lệnh gọi vararg mọi lúc. Bạn không thể tối ưu hóa điều này bằng cách giải nén mảng vì nó sẽ được sao chép trong mọi cuộc gọi. Mặt khác, những gì bạn có thể làm là trích xuất logic vào một thể hiện so sánh tĩnh và sử dụng lại nó:

class Foo(val a: String, val b: Int, val c: Date) : Comparable<Foo> {

    override fun compareTo(other: Foo) = comparator.compare(this, other)

    companion object {
        // using the method reference syntax as an alternative to lambdas
        val comparator = compareBy(Foo::a, Foo::b, Foo::c)
    }
}

4
Lưu ý rằng nếu bạn đang sử dụng nhiều hàm lambdas (có quá tải với chính xác một hàm trong dòng), chúng không được nội tuyến . Có nghĩa là mỗi cuộc gọi đến comapreTo sẽ tạo ra một đối tượng mới. Để tránh điều đó, bạn có thể di chuyển các bộ chọn sang đối tượng đồng hành để các bộ chọn chỉ được cấp phát một lần. Tôi tạo ra một snipped đây: gist.github.com/PaulWoitaschek/7f3c4d5310a66ed4984785ee2d6f70ed
Paul Woitaschek

1
@KirillRakhman Nó tạo ra độc thân cho các chức năng nhưng vẫn phân bổ các mảng:ANEWARRAY kotlin/jvm/functions/Function1
Paul Woitaschek

1
Bắt đầu với Kotlin 1.1.3 compareByvới nhiều lambdas sẽ không phân bổ mảng mới cho mỗi lần compareTogọi.
Ilya

1
@Ilya, bạn có thể chỉ cho tôi đến bảng thay đổi liên quan hoặc thông tin khác cho loại tối ưu hóa này không?
Kirill Rakhman,


0

Nếu bạn muốn sắp xếp theo thứ tự giảm dần, bạn có thể sử dụng câu trả lời được chấp nhận:

list.sortedWith(compareByDescending<Foo> { it.a }.thenByDescending { it.b }.thenByDescending { it.c })

Hoặc tạo một chức năng mở rộng như compareBy:

/**
 * Similar to
 * public fun <T> compareBy(vararg selectors: (T) -> Comparable<*>?): Comparator<T>
 *
 * but in descending order.
 */
public fun <T> compareByDescending(vararg selectors: (T) -> Comparable<*>?): Comparator<T> {
    require(selectors.size > 0)
    return Comparator { b, a -> compareValuesByImpl(a, b, selectors) }
}

private fun <T> compareValuesByImpl(a: T, b: T, selectors: Array<out (T) -> Comparable<*>?>): Int {
    for (fn in selectors) {
        val v1 = fn(a)
        val v2 = fn(b)
        val diff = compareValues(v1, v2)
        if (diff != 0) return diff
    }
    return 0
}

và sử dụng: list.sortedWith(compareByDescending ({ it.a }, { it.b }, { it.c })).


0

Nếu bạn cần sắp xếp theo nhiều trường và một số trường bằng cách giảm dần và những trường khác bằng cách tăng dần, bạn có thể sử dụng:

YOUR_MUTABLE_LIST.sortedWith(compareBy<YOUR_OBJECT> { it.PARAM_1}.thenByDescending { it.PARAM_2}.thenBy { it.PARAM_3})

1
Điều này được bao gồm trong câu trả lời của tôi.
Kirill Rakhman
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.