Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin: Iterate through list and add items at index

I have a (mutable) list with items. I want to iterate through this list and check each item for a specific condition. When the condition is met, I want to insert (not replace) a new item at the specific index.
What is the most elegant way to do this in Kotlin?
P.S. I do not have a code sample yet, because I can't think of a good way to do it.

Edit: Use Case: I want to add headers for a list view for a specific group of items.

like image 648
Thommy Avatar asked Aug 10 '18 13:08

Thommy


3 Answers

If you have a mutable list, you can get its mutable listIterator and modify the list with that iterator during the iteration.

For example to insert a string after each item, that represents an even number:

val list = mutableListOf("1", "2", "3", "4")

val iterator = list.listIterator()
for (item in iterator) {
    if (item.toInt() % 2 == 0) {
        iterator.add("^ That was even")
    }
}

list.forEach { println(it) }    

Or to insert a string before the current one:

val iterator = list.listIterator()
for (item in iterator) {
    if (item.toInt() % 2 == 0) {
        iterator.previous()
        iterator.add("Next will be even:")
        iterator.next()
    }
}

list.forEach { println(it) }
like image 127
Ilya Avatar answered Sep 27 '22 22:09

Ilya


As you said you iterate over the list, maybe a flatMap is rather something for you (in this example I add "odd", "even" after elements that are odd/even):

val list = listOf("1", "2", "3", "4")
val newList = list.flatMap {
      // well... actually your conditions...
      when (it.toInt() % 2) {
        0 -> listOf(it, "<-- that was even" /*, whatever you want to add after it */)
        else -> listOf(it, "<-- that was odd" /*, whatever you want to add after it */)
      }
    }

This will now return you a new list containing the elements you constructed, e.g.

[1, <-- that was odd, 2, <-- that was even, 3, <-- that was odd, 4, <-- that was even]

This way you do not necessarily need the mutable list. And you rather quickly grasp what is concatenated into the list (sequence/whatever).

If you really want to keep the mutable list and work with it, you will need to ensure that you do not alter the list, while you are operating on it, otherwise you get ConcurrentModificationExceptions. This on the other side means that you need to hold the indices where you want to insert things. As soon as you insert them your hold indices are invalid however if any of your hold indices are higher then a previously added one. Except you move your cursor too (i.e. add to the index the amount of previously added elements). Easier then this is just to insert them backwards. It follows a sample where the list must match the keys of a map and will insert its values:

val list = mutableListOf("1", "2", "3", "4", "5", "6")

val itemsToInsertAfterMatch = mapOf("1" to "odd 1", "2" to "even", "3" to "odd", "4" to "ev4n")
list.mapIndexedNotNull { index, s -> itemsToInsertAfterMatch[s]?.let { index to it } }
    .reversed() // actually this ensures that we are operatoring on a separate list while we will add elements later
    .forEach { list.add(it.first, it.second) }

Still, I can't recommend it. If you do not must (and who forces you?), I wouldn't use such a construct. It rather hides what is going on (e.g. what is matched with what? and what is inserted when where?).

All the above solutions did not yet deal with the case when an element wasn't found (e.g. indexOf returns -1 or itemsToInsertAfterMatch[s] == null). It should be easy enough though to add that case.

Note that if what you are trying to insert is static, @mTaks solution with the indices is of course easier then the mapping I've presented above. Depending on how complex your condition is, the indices themselvses will not suffice you. And I can only recommend you to use the flatMap instead. It's way easier to read when you come back. The indices/mapIndexed-solution rather hide what's going on. You may need to decipher that every time you come back to it. You don't even think in indices, so why bother? But maybe that's only my opinion ;-)

like image 43
Roland Avatar answered Sep 27 '22 22:09

Roland


I solved it using groupBy

val map = mutableList.map {
    val groupItems = it.groupBy { (it as ListItem).section}
    val mappedItems = mutableListOf<ListBaseItem>()
    groupItems.forEach { section, items ->
        mappedItems.add(ListHeaderItem(section, section))
        mappedItems.addAll(items)
    }
    mappedItems
}
like image 35
Thommy Avatar answered Sep 27 '22 22:09

Thommy