Kotlin에서 여러 값을 기준으로 정렬 / 비교하는 방법은 무엇입니까?


84

a가 class Foo(val a: String, val b: Int, val c: Date)있고 Foo세 가지 속성을 모두 기반으로 s 목록을 정렬하고 싶습니다 . 어떻게해야합니까?

답변:


146

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바이트 코드에 인라인되지 않으므로 람다에 대해 익명 클래스가 생성됩니다. 그러나 람다 자체가 상태를 캡처하지 않으면 매번 람다를 인스턴스화하는 대신 싱글 톤 인스턴스가 사용됩니다.

의견에서 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
여러 개의 람다 함수를 사용하는 경우 (정확히 인라인되는 하나의 오버로드가 있음) 인라인되지 않습니다 . 즉, comapreTo를 호출 할 때마다 새 개체가 생성됩니다. 선택기가 한 번만 할당되도록 선택기를 컴패니언 개체로 이동할 수 없도록합니다. : 나는 여기 냈다 만들어 gist.github.com/PaulWoitaschek/7f3c4d5310a66ed4984785ee2d6f70ed
폴 Woitaschek

1
이 기능에 대한 싱글을 생성하지만, 여전히 배열을 할당 @KirillRakhman :ANEWARRAY kotlin/jvm/functions/Function1
폴 Woitaschek

1
compareBy여러 람다가 있는 Kotlin 1.1.3부터는 모든 compareTo호출 에 새 배열을 할당하지 않습니다 .
Ilya

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.