Compare two collections, but only by certain fields

Let’s say I’ve got two lists of a custom type. I want to compare the two lists and verify the contents of the lists match each other. Order does not matter - only the presence of the object does.

The simplest way to do this would be to do this solution as described on Baeldung:

list1.size == list2.size && list1.toSet() == list2.toSet()

But there is another catch: I only want to compare certain fields of the contents of the set. I don’t care about the rest of the fields. How can I do something like this above, but only compare specific fields? Let’s say the lists are List<MyType> and I only want to compare MyType.firstName and MyType.lastName

First, I have to say that I don’t think that this a complete solution for comparing two lists ignoring order - because it does not handle duplicates correctly. It checks only for the number of duplicates, but not whether it is the same duplicates, e.g.

fun main() {
    val list1 = listOf(1,1,2)
    val list2 = listOf(1,2,2)
    print("Are these lists equal ignoring order? ")
    println(list1.size == list2.size && list1.toSet() == list2.toSet()) // true
}

One could say that duplicates are irrelevant, but why would you check for their total number then?

===
Now to the actual problem: The source uses the set transformation to stay in linear complexity. For that it uses the constant complexity of set lookup which relies on equal elements having the same hash value.

If you want to compare by only a part of the properties (presumably without changing the types themselves, i.e. modifying their hash value method to only be based on these comparing properties), you can compare to sets with substitute objects:

/// ignores duplicates and order of elements.
fun <T, U> List<T>.myEquals(other: List<T>, compareBy: (T) -> U): Boolean {
    return map(compareBy).toSet() == other.map(compareBy).toSet()
}

Here some sample calls to the method:

fun main() {
    val list1 = listOf(Pair("Important", "Ignore this!"))
    val list2 = listOf(Pair("Important", "Ignore that too!"))

    println("Are these lists equal ignoring order and duplicates? ")
    println(list1.myEquals(list2, { it.first })) // true
    println(list1.myEquals(list2, { listOf(it.second, it.first) })) // false
    val list3 = listOf(Pair("Different value", "Ignore this!"))
    println(list1.myEquals(list3, { it.first })) // false
}

Note: The substitute object can be anything you want - it only needs to have a correct hash implementation.

The method would also work for Collection<T> instead of List<T> and you maybe want to give it a better name than myEquals.

Live long and prosper,
Tlin47

1 Like
list1.indices.all{ list1[it].a == list2[it].b}

Note that list1.indices.all{ list1[it].a == list2[it].b} doesn’t do what’s asked, because:
i) it compares one item’s .a to another’s .b;
ii) it compares each element to an element with a matching index, i.e. it expects the elements to be in the same order in the two lists.

1 Like