如何在 Kotlin 中基于 on/compare 多个值进行排序?
How to sort based on/compare multiple values in Kotlin?
假设我有一个 class Foo(val a: String, val b: Int, val c: Date)
,我想根据所有三个属性对 Foo
的列表进行排序。我该怎么做?
Kotlin 的标准库为此提供了许多有用的辅助方法。
首先,您可以使用 compareBy()
method and pass it to the sortedWith()
扩展方法定义一个比较器来接收列表的排序副本:
val list: List<Foo> = ...
val sortedList = list.sortedWith(compareBy({ it.a }, { it.b }, { it.c }))
其次,您可以让 Foo
使用 compareValuesBy()
辅助方法实现 Comparable<Foo>
:
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 })
性能考虑
compareValuesBy
的 vararg
版本未内联在字节码中,这意味着将为 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)
}
}
如果要降序排列,可以使用已接受的答案:
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 }))
.
如果您需要按多个字段排序,一些字段按降序排序,另一些字段按升序排序,您可以使用:
YOUR_MUTABLE_LIST.sortedWith(compareBy<YOUR_OBJECT> { it.PARAM_1}.thenByDescending { it.PARAM_2}.thenBy { it.PARAM_3})
假设我有一个 class Foo(val a: String, val b: Int, val c: Date)
,我想根据所有三个属性对 Foo
的列表进行排序。我该怎么做?
Kotlin 的标准库为此提供了许多有用的辅助方法。
首先,您可以使用 compareBy()
method and pass it to the sortedWith()
扩展方法定义一个比较器来接收列表的排序副本:
val list: List<Foo> = ...
val sortedList = list.sortedWith(compareBy({ it.a }, { it.b }, { it.c }))
其次,您可以让 Foo
使用 compareValuesBy()
辅助方法实现 Comparable<Foo>
:
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 })
性能考虑
compareValuesBy
的 vararg
版本未内联在字节码中,这意味着将为 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)
}
}
如果要降序排列,可以使用已接受的答案:
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 }))
.
如果您需要按多个字段排序,一些字段按降序排序,另一些字段按升序排序,您可以使用:
YOUR_MUTABLE_LIST.sortedWith(compareBy<YOUR_OBJECT> { it.PARAM_1}.thenByDescending { it.PARAM_2}.thenBy { it.PARAM_3})