Kotlin: Iterate through list and add items at index

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



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.





Will you check the newly added item for the condition as well?
– EpicPandaForce
Aug 10 at 13:33





What is the use case? Even though I added a possible solution I can't really think of a use case that makes this worth it.
– Roland
Aug 10 at 13:57





Depending on what you mean by "specific index", flatMap might do exactly what you want. It is creating a new Map though, and not updating the old one.
– Paul Georg Podlech
Aug 10 at 14:08



flatMap




4 Answers
4



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):


flatMap


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:


ConcurrentModificationException


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.


indexOf


-1


itemsToInsertAfterMatch[s] == null



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 ;-)


flatMap


indices


mapIndexed



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


listIterator



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)



This is my approach:


val list = mutableListOf("ab", "CD", "ef", "gH")

list.indices
.filter list[it].toLowerCase() == list[it]
.mapIndexed index, it -> it + index
.forEach list.add(it, "XX")

list.forEach println(it)



will print: XX ab CD XX ef gH


XX ab CD XX ef gH



The condition is if the list element is all lower case it inserts the element "XX" before.



If your concern is only about elegance, or you want to explore Kotlin's capabilities for academic or research reasons that's fine, but I'm not sure that always a fancy way is the most efficient way.

Why not use a traditional while loop:


while


var index = 0

while (index < list.size)
if (list[index].toLowerCase() == list[index])
list.add(index, "XX")
index++


index++



It's not fancy but for sure it is efficient and maybe more efficient than the fancy way.



I solved it using groupBy


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





Care to explain? Because this code does not seem to be related to the question as it was phrased.
– user8959091
Aug 13 at 17:06





Yes it is, as in the question said, I want to add headers to a listview. I know group my list, and create a group for each unique section item. Then add the header to each group and combine the groups to a list again.
– Thommy
Aug 15 at 7:53






as in the question said really? You posted a question that had nothing to do with headers and groups, then 3 days later you edited it and mention headers in a list view without giving a clue and later on you give an "answer" that you only can relate to the question?
– user8959091
Aug 15 at 8:00





I edited since it was not clear what I asked. And then I got a solution and posted it. This is a site where people help each other. Don't know if you delete every question you answerd yourself, but I don't do that, maybe the answer I found helps others.
– Thommy
Aug 15 at 8:13






You edited 3 days later after you got 3 answers for the original question and did not give any specific info about the headers and groups. So the answer you provided has nothing to do the original question. You should delete the question and repost it with all the relevant clarifications,
– user8959091
Aug 15 at 10:50






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard