使用子类型特定行为 Kotlin 对混合列表进行排序

Sort mixed list using subtype specific behaviour Kotlin

我有一个共享基数 class 但非常不同的项目列表,我想先按它们的 class 对它们进行排序,然后再按特定于 [=25] 的比较器对它们进行排序=].

例如 SubtypeA 应该按 属性 foo 排序,它特定于 class,而 SubTypeB 应该按属性排序 bar1 然后 bar2 特定于 class,我需要对包含两者的列表进行排序。

排序的实际顺序并不重要,只要它在运行时内是一致的,这样就可以通过首先对这些对象的两个列表进行排序来检查它们是否相等。

(到目前为止,我只是使用了一个惰性 属性 将对象添加到集合并检查其索引,从而为相等的对象提供相同的排序键,但是这个缓存正在增长到可笑的大小让事情变得很慢)

如何按 class 对这些对象进行分组,然后使用 class 特定比较器?

这可能会满足您的需求。

我们为所有子类型提供单独的比较器,并简单地按类型分组,然后用它自己的比较器对每个组进行排序,然后简单地按 class 名称对组进行排序,并以连续的方式展平每个组中的元素.

这是演示它的代码示例。

  • 添加了实用函数 sortedUsing 来执行此排序
  • 添加了一个实用程序类型 TypeComparator 只是为了更方便地传递类型安全 Class + Comparator
fun <T : Any> List<T>.sortedUsing(
        vararg typeComparators: TypeComparator<out T>
): List<T> {

    @Suppress("UNCHECKED_CAST")
    fun <R : T> TypeComparator<R>.sort(list: List<T>): List<R> = (list as List<R>).sortedWith(comparator)

    val comparators = typeComparators.associateBy { it.type }
    return groupBy { it::class }
            .mapValues { (klass, list) ->
                val typeComparator = comparators[klass]
                        ?: typeComparators.firstOrNull { klass.isSubclassOf(it.type) }
                        ?: list.firstOrNull()?.tryMakeComparator()
                        ?: throw IllegalArgumentException("Missing comparator for type: $klass")
                typeComparator.sort(list)
            }
            .map { it }
            .sortedBy { it.key.qualifiedName }
            .flatMap { it.value }
}

@Suppress("UNCHECKED_CAST")
private fun <T : Any> T.tryMakeComparator(): TypeComparator<T>? {
    if (this !is Comparable<*>) {
        return null
    }
    return TypeComparator(this::class as KClass<T>, Comparator { o1, o2 ->
        val c1 = o1 as Comparable<Comparable<T>>
        val c2 = o2 as Comparable<T>
        c1.compareTo(c2)
    })
}

data class TypeComparator<T : Any>(
        val type: KClass<T>,
        val comparator: Comparator<T>
)

如果愿意,您也可以为类型提供比较器,因为上面代码段中的默认设置是按 class 全名对类型组进行排序。

通过一些更好的方法来累积某种类型的元素,您可以摆脱未经检查的列表转换。

用法示例:

open class Base

data class SubtypeA(
        val foo: Int
) : Base()

data class SubtypeB(
        val bar1: String,
        val bar2: String
) : Base()

fun main() {
    val list = listOf(
            SubtypeA(5),
            SubtypeB("foo", "x"),
            SubtypeA(42),
            SubtypeA(2),
            SubtypeB("bar", "y"),
            SubtypeB("bar", "x")
    )
    val sorted = list.sortedUsing(
            TypeComparator(SubtypeA::class, Comparator.comparing { a: SubtypeA -> a.foo }),
            TypeComparator(SubtypeB::class, Comparator.comparing { b: SubtypeB -> b.bar1 }.thenComparing { b: SubtypeB -> b.bar2 })
    )
    sorted.forEach { println(it) }
    // prints:
    //   SubtypeA(foo=2)
    //   SubtypeA(foo=5)
    //   SubtypeA(foo=42)
    //   SubtypeB(bar1=bar, bar2=x)
    //   SubtypeB(bar1=bar, bar2=y)
    //   SubtypeB(bar1=foo, bar2=x)

}