如何在Kotlin中基于/比较多个值进行排序?


Answers:


145

Kotlin的stdlib为此提供了许多有用的帮助程序方法。

首先,您可以使用compareBy()方法定义比较器,并将其传递给sortedWith()扩展方法以接收列表的排序副本:

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

其次,你可以让Foo实现Comparable<Foo>使用compareValuesBy()helper方法:

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 })
}

然后,您可以调用sorted()不带参数的扩展方法以接收列表的排序副本:

val sortedList = list.sorted()

排序方向

如果您需要对某些值升序排序,而对其他值降序排序,则stdlib还提供了以下功能:

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

性能考量

字节码中未内嵌的vararg版本,compareValuesBy这意味着将为lambda生成匿名类。但是,如果Lambda本身不捕获状态,则将使用单例实例,而不是每次都实例化Lambda。

正如Paul Woitaschek在评论中所指出的,与多个选择器进行比较将每次实例化一个用于vararg调用的数组。您无法通过提取数组来优化此方法,因为它将在每次调用时复制。另一方面,您可以做的是将逻辑提取到静态比较器实例中并重新使用它:

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
请注意,如果您使用多个lambdas函数(有一个内联的重载函数),则不会内联它们。这意味着每次调用comapreTo都会创建一个新对象。为防止将选择器移动到伴随对象,因此选择器仅分配一次。我在这里创建了一个片段
Paul Woitaschek '17

1
@KirillRakhman它为函数创建单例,但仍在分配数组:ANEWARRAY kotlin/jvm/functions/Function1
Paul Woitaschek

1
从具有compareBy多个lambda的Kotlin 1.1.3开始,不会在每次compareTo调用时分配新的数组。
伊利亚2015年

1
@Ilya您能否将我指向相关的更新日志或此类优化的其他信息?
Kirill Rakhman


0

如果要按降序排序,可以使用接受的答案:

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

或创建一个扩展函数,如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
}

并使用:list.sortedWith(compareByDescending ({ it.a }, { it.b }, { it.c }))


0

如果您需要按多个字段进行排序,而某些字段需要按降序排序,而另一些则需要按升序排序,则可以使用:

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

1
我的回答涵盖了这一点。
Kirill Rakhman
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.