Kotlinで複数の値に基づいて並べ替え/比較する方法は?


85

がありclass Foo(val a: String, val b: Int, val c: Date)Foo3つのプロパティすべてに基づいてのリストを並べ替えたいとします。どうすればいいですか?

回答:


149

Kotlinのstdlibは、このための便利なヘルパーメソッドをいくつか提供しています。

まず、compareBy()メソッドを使用してコンパレータを定義し、それをsortedWith()拡張メソッドに渡して、リストのソートされたコピーを受け取ることができます。

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

次に、ヘルパーメソッドを使用してFoo実装させることができます。Comparable<Foo>compareValuesBy()

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バイトコードにインライン化されていないため、ラムダに対して匿名クラスが生成されます。ただし、ラムダ自体が状態をキャプチャしない場合は、ラムダを毎回インスタンス化する代わりに、シングルトンインスタンスが使用されます。

で述べたように、ポールWoitaschekコメントの中で、複数のセレクタと比較すると、可変引数呼び出し毎回の配列をインスタンス化します。呼び出しごとにコピーされるため、配列を抽出してこれを最適化することはできません。一方、実行できることは、ロジックを静的コンパレータインスタンスに抽出し、それを再利用することです。

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
複数のラムダ関数を使用している場合(インライン化されたものが1つだけのオーバーロードがある場合)、それらはインライン化さないことに注意してください。つまり、comapreToを呼び出すたびに、新しいオブジェクトが作成されます。これを防ぐには、セレクターをコンパニオンオブジェクトに移動して、セレクターが1回だけ割り当てられるようにします。ここでスニップを作成しました:gist.github.com/PaulWoitaschek/7f3c4d5310a66ed4984785ee2d6f70ed
Paul Woitaschek 2017年

1
これは、機能のためのシングルトンを作成しますが、それでも配列を割り当て@KirillRakhman:ANEWARRAY kotlin/jvm/functions/Function1
ポールWoitaschek

1
Kotlin 1.1.3以降ではcompareBy、複数のラムダでは、すべての新しい配列割り当てないだろうcompareToコールを。
イリヤ2017年

1
@Ilyaこの種の最適化に関連する変更ログまたはその他の情報を教えていただけますか?
Kirill Rakhman 2017


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
これは私の答えでカバーされています。
KirillRakhman20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.