Authors Top

If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

1. Overview

List is a pretty common collection type in Kotlin.

In this tutorial, we’ll learn how to compare two lists in Kotlin.

2. Introduction to the Problem

A List is an ordered collection of elements. Further, a List can contain duplicate values.

Therefore, when we’re talking about comparing two lists, depending on the requirement, there are a couple of scenarios in which we consider two lists are equal:

  • Two lists have the same size, contain the same elements, and are in the same order.
  • Two lists have the same size and contain the same elements. However, we don’t care about the order of the elements.

An example may explain it quickly. Let’s say we have a target list:

private val targetList = listOf("one", "two", "three", "four", "five")

As the code above shows, we have five elements in targetList.

Then, let’s create some more lists of different cases:

private val listExactlySame = listOf("one", "two", "three", "four", "five")
private val listInDiffSizeButWithSameElements = listOf("one", "two", "three", "four", "five", "five", "five")
private val listInDiffSizeAndElements = listOf("one", "two", "three", "four", "five", "five", "five", "I am a new element")
private val listInDiffOrder = listOf("two", "one", "three", "five", "four")
private val listInDiffElement = listOf("ONE", "two", "three", "four", "FIVE")

Next, let’s build methods to cover the two comparison scenarios we mentioned above to determine if these lists are the “same” as our targetList.

For simplicity, we’ll use unit test assertions to verify whether our comparison methods work as expected.

3. Comparing Elements and the Order

In Kotlin, we use structural equality (==) to evaluate if both values are the same or equal. This is the same as the equals() method in Java.

Therefore, if list1 == list2 is true, both lists have the same size and contain the same elements in exactly the same order.

Next, let’s test it with our lists:

targetList.let {
    assertThat(listExactlySame == it).isTrue
    assertThat(listInDiffOrder == it).isFalse
    assertThat(listInDiffSizeButWithSameElements == it).isFalse
    assertThat(listInDiffSizeAndElements == it).isFalse
    assertThat(listInDiffElement == it).isFalse
}

As the code above shows, if we compare two lists using structural equality, only listExactlySame and targetList should be equal. If we run the test, it passes.

So, == is the most straightforward way to compare two lists to see if they are equal.

4. Compare Elements Ignoring the Order

When we’re facing the “ignoring order” requirement, some of us may come up with this idea: list1.size() == list2.size() && list1.containsAll(list2) &&list2.containsAll(list1).

Indeed, this solves the problem. However, List‘s containsAll is an expensive method. As List‘s look-up costs O(N). Thus, given that both lists have the same size, list1.containsAll(list2)’s cost is O(N^2).

To get better performance, we can use Set to solve the problem. The lookup function on HashSet costs only O(1). Therefore, we can solve the problem by first converting both lists to HashSets and then checking the structural equality of the two sets. The total cost will be O(N).

Now that we understand what to do, the implementation won’t be a challenge at all:

fun <T> equalsIgnoreOrder(list1:List<T>, list2:List<T>) = list1.size == list2.size && list1.toSet() == list2.toSet()

It’s worth mentioning that Kotlin’s List.toSet() function returns a LinkedHashSet object, which is a sub-type of HashSet.

The implementation is pretty straightforward. However, to make the function call fluent, we can create an extension function on List:

fun <T> List<T>.equalsIgnoreOrder(other: List<T>) = this.size == other.size && this.toSet() == other.toSet()

In this way, the function call will look like list1.equalsIgnoreOrder(list2).

Moreover, as the extension function has only one parameter, we can add the infix notation to make the function call more easy-to-read:

infix fun <T> List<T>.equalsIgnoreOrder(other: List<T>) = this.size == other.size && this.toSet() == other.toSet()

if (list1 equalsIgnoreOrder list2) ....

Now, let’s test it using our example lists:

targetList.let {
    assertThat(listExactlySame equalsIgnoreOrder it).isTrue
    assertThat(listInDiffOrder equalsIgnoreOrder it).isTrue
    assertThat(listInDiffSizeButWithSameElements equalsIgnoreOrder it).isFalse
    assertThat(listInDiffSizeAndElements equalsIgnoreOrder it).isFalse
    assertThat(listInDiffElement equalsIgnoreOrder it).isFalse
}

As the assertions above show if we ignore the order of the elements, listInDiffOrder and listExactlySame should be equal to our targetList.

The test passes when we execute it. Therefore, our equalsIgnoreOrder function works as expected.

5. Conclusion

In this article, we’ve explored how to compare two List objects with or without checking the elements’ order.

Additionally, we’ve learned that using Kotlin’s extension function and infix notation can help write easy-to-read code.

As always, the complete source code used in the article can be found on GitHub.

Authors Bottom

If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

2 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!