How to sort a collection by multiple values AND nulls at the end using the available convenient methods of Kotlin

Viewed 2197

I've used the sortedWith and compareBy methods before and it is capable of sorting using multiple parameters.

I've also used nullsLast method before and it is quite convenient just like the other two.

With these methods, however, I can't figure out a way to sort a collection with the following sorting rules:

Given the Class:

data class MyClass(
    val varA: String?,
    val varB: String
)
  • sort according to varA alphabetically and nulls/blank to be last; then

  • sort according to varB alphabetically

So let's say I have the following collection:

val collection = listOf(
    MyClass(null, "A"),
    MyClass("a", "B"),
    MyClass("c", "C"),
    MyClass("b", "D"),
    MyClass("", "E"),
    MyClass("a", "F"),
    MyClass("b", "G"),
    MyClass(null, "H"),
    MyClass("", "I")
)

it should then be sorted to:

    MyClass("a", "B")
    MyClass("a", "F")
    MyClass("b", "D")
    MyClass("b", "G")
    MyClass("c", "C")
    MyClass(null, "A")
    MyClass("", "E")
    MyClass(null, "H")
    MyClass("", "I")

is there a one-liner code I can use just like the compareBy that uses vararg parameter

2 Answers

I would do it like this (see kotlin.comparisons documentation):

val comparator = compareBy<String, MyClass>(nullsLast(), { it.varA.let { if (it == "") null else it } }).thenBy { it.varB }

collection.sortedWith(comparator)

Of course there is a one-liner for this:

collection.sortedBy {if(it.varA != null && it.varA.isNotEmpty()) it.varA + it.varB else "z" + it.varB}

Then print it:

output.forEach { println("Myclass(${it.varA}, ${it.varB})") }

Output:

Myclass(a, B)
Myclass(a, F)
Myclass(b, D)
Myclass(b, G)
Myclass(c, C)
Myclass(null, A)
Myclass(, E)
Myclass(null, H)
Myclass(, I)

Explanation:

We need to split the cases when we sort by varA and when by varB here. That is why there is a condition if(it.varA != null && it.varA.isNotEmpty()), which says that we will sort by varA only if its value isn't null or "".

Otherwise, we sort by string "z" + it.varB, which means that we sort by varB, but with z prefix, which will ensure that these items will be at the end of sorted collection.

To understand more easily how it works, you can try following code:

    val output = collection.map { if(it.varA != null && it.varA.isNotEmpty()) it.varA + it.varB else "z" + it.varB }

    output.sortedBy { it }.forEach { println(it) }

This one will output these strings:

aB
aF
bD
bG
cC
zA
zE
zH
zI

Now it should be more obvious. :-)

Related