mickp
mickp

Reputation: 1799

How to merge two lists of objects based on IDs?

I have two lists. One is called oldList and contains old data, the 2nd one is called updateList and contains a set of updates.

I want to create a new list with those rules:

I came up with a simple code:

Item.kt

data class Item (val id: String, val text: String)

Main.kt

fun main() {

    val oldList: List<Item> = listOf(
        Item("aaa1", "aaa2"),
        Item("bbb1", "bbb2"),
        Item("ddd1", "ddd2"))

    val updateList: List<Item> = listOf(
        Item("aaa1", "aaa3"),
        Item("ccc1", "ccc2"))

    val resultList = oldList.toMutableList()

    for (item in updateList) {
        val index = oldList.indexOfFirst { it.id == item.id }
        if (index < 0) {
            resultList.add(item)
        } else {
            resultList[index] = item
        }
    }

    println(resultList)
}

This works fine but I can imagine it's inefficient and there might also be some nice Kotlin idiom. Is there a better solution?

Upvotes: 9

Views: 8412

Answers (3)

Tenfour04
Tenfour04

Reputation: 93551

If the original order doesn't matter, you can combine the lists, putting the new values first to favor them in a distinctBy call:

val resultList = (updateList + oldList).distinctBy(Item::id)

If the order matters exactly as you described it, you can convert both lists to maps before combining them. When combining two Maps with +, the items in the second Map take precedence, but the order from the first Map is preserved.

val resultList = 
    (oldList.associateBy(Item::id) + updateList.associateBy(Item::id)).values.toList()

Upvotes: 12

jsamol
jsamol

Reputation: 3232

You can use the distinctBy method:

val resultList = (updateList + oldList).distinctBy { it.id }

What distinctBy does is return a new list containing only elements having distinct keys returned by the provided lambda. You must bear in mind however that the order of elements matters and the first encountered item with the given key is kept in the result list, the others are ignored.

Upvotes: 2

goedi
goedi

Reputation: 2083

If you override equals and hashcode to use only id, as it seems to be the right thing to do as if your class uses an id I'm not sure if it is data class

data class Item(val id: String,
                  val text: String) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other?.javaClass != javaClass) return false

        other as Item

        if (id != other.id) return false

        return true
    }

    override fun hashCode(): Int {
        return id.hashCode()
    }
}

then you can just make an union of the updateList with the old one

fun main() {

    val oldList: List<Item> = listOf(
        Item("aaa1", "aaa2"),
        Item("bbb1", "bbb2"),
        Item("ddd1", "ddd2"))

    val updateList: List<Item> = listOf(
        Item("aaa1", "aaa3"),
        Item("ccc1", "ccc2"))

    val resultList = updateList.union(oldList)

    println(resultList)
}

Upvotes: 1

Related Questions