Comparator in Kotlin (original) (raw)

In programming contexts, as there arises a need for a new type, there is also a major task of ordering the instances of a type. To compare two instances of a type we implement Comparable interface. However, since in ordering instances they must be compared automatically and also since the order can vary according to various parameters, Kotlin provides a simple **Comparator interface. This interface compares two objects of a type and arranges them in an order.

Comparable vs Comparator

The compare Function

At the core of every comparator is the compare function. This function compares two instance of a type and returns zero if both are equal, a negative number if second instance is bigger otherwise returns a positive number.

abstract fun compare(a: T, b: T): Int

This function returns **0 if a and b are equal, a **negative number if a comes before b and a **positive number if a comes after b.

Comparator Extension Functions

**1. reversed():

If we have a comparator but want to reverse the order (e.g., from ascending to descending), we can use the reversed() function.

fun Comparator.reversed(): Comparator

It returns a new comparator with the reverse order.

**2. then():

Sometimes, the first comparison may not decide the order (e.g., two people have the same first name). In such cases, we can add a second comparator using then().

infix fun Comparator.then(
comparator: Comparator
): Comparator

This means: "First compare by A, but if A is equal, then compare by B".

**Example:

Kotlin `

fun main() { data class Player(val firstName: String, val lastName: String)

val players = listOf(
    Player("Steve", "Waugh"),
    Player("Steve", "Smith"),
    Player("Virat", "Kohli"),
    Player("Kane", "Williamson"),
    Player("Joe", "Root")
)

println("Original List:")
println(players)

// Sort by first name
val firstNameComparator = compareBy<Player> { it.firstName }
val sortedByFirstName = players.sortedWith(firstNameComparator)
println("Sorted by First Name:")
println(sortedByFirstName)

// Sort by first name, then last name
val sortedByFirstThenLast = players.sortedWith(
    firstNameComparator.then(compareBy { it.lastName })
)
println("Sorted by First Name then Last Name:")
println(sortedByFirstThenLast)

// Reverse order
val reversed = players.sortedWith(firstNameComparator.reversed())
println("Reversed Order:")
println(reversed)

}

`

**Output:

Original List:
[Player(firstName=Steve, lastName=Waugh), Player(firstName=Steve, lastName=Smith), Player(firstName=Virat, lastName=Kohli), Player(firstName=Kane, lastName=Williamson), Player(firstName=Joe, lastName=Root)]
Sorted by First Name:
[Player(firstName=Joe, lastName=Root), Player(firstName=Kane, lastName=Williamson), Player(firstName=Steve, lastName=Waugh), Player(firstName=Steve, lastName=Smith), Player(firstName=Virat, lastName=Kohli)]
Sorted by First Name then Last Name:
[Player(firstName=Joe, lastName=Root), Player(firstName=Kane, lastName=Williamson), Player(firstName=Steve, lastName=Smith), Player(firstName=Steve, lastName=Waugh), Player(firstName=Virat, lastName=Kohli)]
Reversed Order:
[Player(firstName=Virat, lastName=Kohli), Player(firstName=Steve, lastName=Waugh), Player(firstName=Steve, lastName=Smith), Player(firstName=Kane, lastName=Williamson), Player(firstName=Joe, lastName=Root)]

thenBy() and thenByDescending()

These functions allow us to easily chain comparisons based on properties that implement Comparable:

fun Comparator.thenBy(
selector: (T) -> Comparable<*>?
): Comparator

**Example:

Kotlin `

fun main() { data class Box(val height: Int, val weight: Int)

val boxes = listOf(
    Box(3, 25),
    Box(5, 50),
    Box(7, 95),
    Box(2, 10),
    Box(4, 10),
    Box(3, 45)
)

// First by height, then by weight
val sortedByHeightThenWeight = boxes.sortedWith(
    compareBy<Box> { it.height }.thenBy { it.weight }
)
println("Sorted by Height then Weight:")
println(sortedByHeightThenWeight)

// First by weight, then by descending height
val sortedByWeightThenHeightDesc = boxes.sortedWith(
    compareBy<Box> { it.weight }.thenByDescending { it.height }
)
println("Sorted by Weight then Height Descending:")
println(sortedByWeightThenHeightDesc)

}

`

**Output:

Sorted by Height then Weight:
[Box(height=2, weight=10), Box(height=3, weight=25), Box(height=3, weight=45), Box(height=4, weight=10), Box(height=5, weight=50), Box(height=7, weight=95)]
Sorted by Weight then Height Descending:
[Box(height=4, weight=10), Box(height=2, weight=10), Box(height=3, weight=25), Box(height=3, weight=45), Box(height=5, weight=50), Box(height=7, weight=95)]

thenComparator() and thenDescending()

These give even more control by allowing us to define custom comparison logic as a lambda expression.

fun Comparator.thenComparator(
comparison: (a: T, b: T) -> Int
): Comparator

**Example:

Kotlin `

fun main() { val pairs = listOf( "A" to 0, "B" to 1, "A" to 3, "G" to 345, "E" to 20, "J" to 0 )

// Sort by string, then by integer
val sortedByStringThenInt = pairs.sortedWith(
    compareBy<Pair<String, Int>> { it.first }
        .thenComparator { a, b -> a.second.compareTo(b.second) }
)
println("Sorted by String then Integer:")
println(sortedByStringThenInt)

// Sort by integer, then by string in descending order
val sortedByIntThenStringDesc = pairs.sortedWith(
    compareBy<Pair<String, Int>> { it.second }
        .thenDescending(compareBy { it.first })
)
println("Sorted by Integer then String Descending:")
println(sortedByIntThenStringDesc)

}

`

**Output:

Sorted by String then Integer:
[(A, 0), (A, 3), (B, 1), (E, 20), (G, 345), (J, 0)]
Sorted by Integer then String Descending:
[(J, 0), (A, 0), (B, 1), (A, 3), (E, 20), (G, 345)]