Search code examples
androidkotlinsortingcollections

Why can't kotlin sortWith order the collection by the first field in descending order and then by the second field in ascending order


I want to order the tasks collection by nextTime in descending order and then timePart in descending order.

the sort code works fine for the person collection, however, when it applies to tasks collection, it does not order by timePart in descending order, which is really weird. The result shows below.

    2023-05-30 23:44:56.205 24842-24863/com.maxim.wordcard I/System.out: Person(name='Olivia', age=20)
    2023-05-30 23:44:56.205 24842-24863/com.maxim.wordcard I/System.out: Person(name='Olivia', age=25)
    2023-05-30 23:44:56.205 24842-24863/com.maxim.wordcard I/System.out: Person(name='Harry', age=10)
    2023-05-30 23:44:56.205 24842-24863/com.maxim.wordcard I/System.out: Person(name='Harry', age=15)

    2023-05-30 23:33:07.059 23361-23381/com.maxim.wordcard I/System.out: Task(nextTime='Wed May 31 23:23:07 GMT+08:00 2023', time=1130)
    2023-05-30 23:33:07.060 23361-23381/com.maxim.wordcard I/System.out: Task(nextTime='Wed May 31 23:13:07 GMT+08:00 2023', time=1120)
    2023-05-30 23:33:07.060 23361-23381/com.maxim.wordcard I/System.out: Task(nextTime='Tue May 30 23:33:07 GMT+08:00 2023', time=1140)
    2023-05-30 23:33:07.060 23361-23381/com.maxim.wordcard I/System.out: Task(nextTime='Tue May 30 23:23:07 GMT+08:00 2023', time=1130).   





package com.maxim.wordcard

import com.maxim.wordcard.database.entity.Task
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import java.util.*

@RunWith(JUnit4::class)
class CollectionSort {

    @Test
    fun testSortCollection() {
        val calendar = Calendar.getInstance()
        val task1 = Task(calendar.time, 1, 1)
        task1.timePart = calendar.get(Calendar.HOUR) * 100 + calendar.get(Calendar.MINUTE) + calendar.get(Calendar.SECOND)
        calendar.add(Calendar.MINUTE, -10)
        val task2 = Task(calendar.time, 1, 1)
        task2.timePart = calendar.get(Calendar.HOUR) * 100 + calendar.get(Calendar.MINUTE) + calendar.get(Calendar.SECOND)
        calendar.add(Calendar.DATE, 1)
        val task3 = Task(calendar.time, 1, 1)
        task3.timePart = calendar.get(Calendar.HOUR) * 100 + calendar.get(Calendar.MINUTE) + calendar.get(Calendar.SECOND)
        calendar.add(Calendar.MINUTE, -10)
        val task4 = Task(calendar.time, 1, 1)
        task4.timePart = calendar.get(Calendar.HOUR) * 100 + calendar.get(Calendar.MINUTE) + calendar.get(Calendar.SECOND)
        val tasks = arrayListOf<Task>(task1, task2, task3, task4)
        tasks.sortWith(compareByDescending<Task> { it.nextTime }.thenBy { it.timePart })
        for (task in tasks) {
            println(task)
        }
    }

    @Test
    fun testPerson() {
        val persons = mutableListOf(
            Person("Olivia", 25),
            Person("Harry", 15),
            Person("Olivia", 20),
            Person("Harry", 10)
        )
        persons.sortWith(compareByDescending<Person>{ it.name }.thenBy { it.age })
        for (person in persons) {
            println(person)
        }
    }

    class Person(val name: String, val age: Int, val test: String?) {

        constructor(name: String, age: Int): this(name, age, "hell")
        override fun toString(): String {
            return "Person(name='$name', age=$age)"
        }
    }
}
    
 

       package com.maxim.wordcard.database.entity
        
        import androidx.room.ColumnInfo
        import androidx.room.Entity
        import androidx.room.Ignore
        import androidx.room.PrimaryKey
        import java.sql.Time
        import java.util.*
        
        @Entity
        data class Task(
            @PrimaryKey(autoGenerate = true) var id: Int?,
            @ColumnInfo var unknown: Int,
            @ColumnInfo var search: Int,
            @ColumnInfo var know: Int,
            @ColumnInfo var startTime: Date,
            @ColumnInfo var nextTime: Date,
        
            /**
             * [TaskStatus]
             */
            @ColumnInfo var status: Int?,
            @ColumnInfo var wordId: Long?,
            @ColumnInfo var courseId: Long?,
            @Ignore var isSkim: Boolean = false,
            @Ignore var isShowDefinition: Boolean = false,
            @Ignore var timePart: Int? = null,
        ) {
            constructor(wordId: Long?, courseId: Long?) : this(null, 0, 0, 0, Date(), Date(), TaskStatus.UNSTART.status, wordId, courseId)
        
            @Ignore
            constructor(nextTime: Date, wordId: Long?, courseId: Long?) : this(null, 0, 0, 0, Date(), nextTime, TaskStatus.UNSTART.status, wordId, courseId)
            /**
             * Task is used as a key, its hashcode should keep the same, hence, exclude some fields which
             * may change due to user interaction.
             */
            override fun hashCode(): Int {
                return id.hashCode() + wordId.hashCode() + startTime.hashCode()
            }
        
            override fun equals(other: Any?): Boolean {
                if (this === other) return true
                if (other !is Task) return false
                return id == other.id && wordId == other.wordId && startTime == other.startTime
            }
        
        //    override fun compareTo(other: Task): Int {
        //        return compareValuesBy(this, other, { it.nextTime }, { it.timePart })
        //    }
        
            override fun toString(): String {
                return "Task(nextTime='$nextTime', time=$timePart)"
            }
        
        
        }

Solution

  • Select the date part and then select the time part to sort. The second sort operation will be executed only when two elements are equal at the first sort operation. The codes in the question malfunction because they have been not equal at the first sort operation. I share the final functional codes as below.

    private var dateFormat = SimpleDateFormat("yyyy-MM-dd")
    private var timeFormat = SimpleDateFormat("HH:mm:ss")
    
    fun updateData(data: MutableMap<Task, Word>) {
        this.data.clear()
        val sortedByDescending = data.keys.sortedWith(
            compareByDescending<Task> { selectDate(it) }.thenBy { selectTime(it) })
        this.data.addAll(sortedByDescending)
    
        notifyDataSetChanged()
    }
    
    private fun selectDate(task: Task): String {
        val result = dateFormat.format(task.nextTime.time)
        return result
    }
    
    private fun selectTime(task: Task): String {
        val result = timeFormat.format(task.nextTime.time)
        return result
    }