3

Given following classes:

interface Item {
    val name: String
}

data class Server(override val name: String, val id: String) : Item
data class Local(override val name: String, val date: Int) : Item
data class Footer(override val name: String) : Item

If we create a list:

val items = arrayListOf<Item>()
items.add(Server("server", "b"))
items.add(Local("local", 2))
items.add(Footer("footer"))
items.add(Server("server", "a"))
items.add(Local("local", 1))
items.add(Footer("footer"))
items.add(Server("server", "c"))
items.add(Local("local", 0))

And sort it:

val groupBy = items.groupBy { it.name }

val partialSort = arrayListOf<Item>()

//individually sort each type
partialSort.addAll(groupBy["local"]!!.map { it as Local }.sortedWith(compareBy({ it.date })))
partialSort.addAll(groupBy["server"]!!.map { it as Server }.sortedWith(compareBy({ it.id })))
partialSort.addAll(groupBy["footer"]!!.map { it as Footer })

//this can be avoided if above three lines are rearranged
val fullSort = partialSort.sortedWith(compareBy({ it is Footer }, { it is Local }, { it is Server }))

Then I get a list which looks like if it was created by following commented code:

//        items.add(Server("server", "a"))
//        items.add(Server("server", "b"))
//        items.add(Server("server", "c"))
//        items.add(Local("local", 0))
//        items.add(Local("local", 1))
//        items.add(Local("local", 2))
//        items.add(Footer("footer"))
//        items.add(Footer("footer"))

Is there a better way to sort it this way? I read How to sort based on/compare multiple values in Kotlin? and Sort collection by multiple fields in Kotlin already but couldn't apply that to my code.

3
  • Is there an assumption that the subclass type and the name property is always the same (case-insensitive)? Commented Mar 24, 2018 at 3:47
  • I would love an answer that doesn't assume that. But sure lets say subclass type and the name property is always same. Commented Mar 24, 2018 at 5:00
  • Did my answer help? Commented Mar 24, 2018 at 20:57

1 Answer 1

8

Yes, it can be achieved in single operation (but pretty complex one) and you're thinking in right way, compareBy can do the trick for you

items.sortWith(compareBy({
    when (it) {
        is Server -> -1
        is Local -> 0
        is Footer -> 1
        else -> Integer.MAX_VALUE
    }
}, {
    when (it) {
        is Server -> it.id
        is Local -> it.date
        else -> 0
    }
}))

What we do here:

  1. We're creating syntetic comparator for implementations of Item. Of course this number may be just another field in interface if that's common usecase.
  2. We're defining fields to compare on Server and Local because they have additional criterion of sorting.
  3. And we're passing comparators created on steps 1 and 2 to compareBy function.

After this operation items collections is sorted:

[Server(name=server, id=a), Server(name=server, id=b), Server(name=server, id=c), Local(name=local, date=0), Local(name=local, date=1), Local(name=local, date=2), Footer(name=footer), Footer(name=footer)]

UPD: If name of Item shoud be subject of sorting too — you can easily add one more comparator like Item::name in appropriate place.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.