Search code examples
kotlincollections

Kotlin transform element with values from previous item in collection


I have a list of objects of type MyObject:

data class MyObject(val name: String?, val age: Int)

I want to go through the list, and for those values with name == null, I'd like to copy the value on the previous item, if present. And if possible id like to carry the changes over, so if item 0 has name, 1 doesnt, and 2 doesnt; 1 should get name from 0, and 2 should get name from 1 which got its name from 0.

I hope this makes sense.

I sense this is something there might be a builtin function for like reduce, scan or something similar but I cant seem to get to the solution?

Example input:

[ MyObject("Paul", 21),
MyObject(null, 23),
MyObject(null, 17),
MyObject("Mike", 45),
MyObject("Laura", 20),
MyObject(null, 60)
]

Example output:

[ MyObject("Paul", 21),
MyObject("Paul", 23),
MyObject("Paul", 17),
MyObject("Mike", 45),
MyObject("Laura", 20),
MyObject("Laura", 60)
]

Solution

  • While there is a built-in scan (aka runningFold), it is more appropriate to use runningReduce (as suggested by lukas.j) here, since you do not need to use a dummy object as the initial value.

    val result = list.runningReduce() { acc, obj ->
        MyObject(obj.name ?: acc.name, obj.age)
    }
    

    This creates a new list with new MyObject instances.

    You can also fold with a mutable list:

    val result = list.fold(mutableListOf<MyObject>()) { list, obj ->
        list.apply {
            val name = obj.name ?: lastOrNull()?.name
            add(MyObject(name, obj.age))
        }
    }
    

    The idea is to use the name of the object last added to the list, if obj.name is null.

    Alternatively, if you want to mutate the MyObjects in place, you should make name a var:

    data class MyObject(var name: String?, val age: Int)
    

    and use zipWithNext:

    list.zipWithNext { prev, curr ->
        if (curr.name == null) {
            curr.name = prev.name
        }
        // shorter but less readable IMO:
        //curr.name = curr.name ?: prev.name
    }