Anuj Mody
Anuj Mody

Reputation: 525

Remove data from list while iterating kotlin

I am new to kotlin programming. What I want is that I want to remove a particular data from a list while iterating through it, but when I am doing that my app is crashing.

for ((pos, i) in listTotal!!.withIndex()) {

            if (pos != 0 && pos != listTotal!!.size - 1) {

                if (paymentsAndTagsModel.tagName == i.header) {
                    //listTotal!!.removeAt(pos)
                    listTotal!!.remove(i)
                }



            }
        }

OR

 for ((pos,i) in listTotal!!.listIterator().withIndex()){
            if (i.header == paymentsAndTagsModel.tagName){
                listTotal!!.listIterator(pos).remove()
            }

        }

The exception which I am getting

java.lang.IllegalStateException

Upvotes: 47

Views: 49594

Answers (9)

Artem_Iens
Artem_Iens

Reputation: 312

Another solution that will suit small collections. For example set of listeners in some controller.

inline fun <T> MutableCollection<T>.forEachSafe(action: (T) -> Unit) {
    val listCopy = ArrayList<T>(this)
    for (element: T in listCopy) {
        if (this.contains(element)) {
            action(element)
        }
    }
}

It makes sure that elements of collection can be removed safely even from outside code.

Upvotes: 0

Renetik
Renetik

Reputation: 6373

People didn't break iteration in previous posts dont know why. It can be simple but also with extensions and also for Map:

fun <T> MutableCollection<T>.removeFirst(filter: (T) -> Boolean) =
    iterator().removeIf(filter)

fun <K, V> MutableMap<K, V>.removeFirst(filter: (K, V) -> Boolean) =
    iterator().removeIf { filter(it.key, it.value) }

fun <T> MutableIterator<T>.removeFirst(filter: (T) -> Boolean): Boolean {
    for (item in this) if (filter.invoke(item)) {
        remove()
        return true
    }
    return false
}

Upvotes: 1

Chris Sprague
Chris Sprague

Reputation: 3574

Typically you can use:

yourMutableCollection.removeIf { someLogic == true }

However, I'm working with an Android app that must support APIs older than 24. In this case removeIf can't be used.

Here's a solution that is nearly identical to that implemented in Kotlin Collections that doesn't rely on Predicate.test - which is why API 24+ is required in the first place

 //This function is in Kotlin Collections but only for Android API 24+
fun <E> MutableCollection<E>.removeIff(filter: (E) -> Boolean): Boolean {
    var removed = false
    val iterator: MutableIterator<E> = this.iterator()
    while (iterator.hasNext()) {
        val value = iterator.next()
        if (filter.invoke(value)) {
            iterator.remove()
            removed = true
        }
    }
    return removed
}

Upvotes: 1

murgupluoglu
murgupluoglu

Reputation: 7194

val numbers = mutableListOf(1,2,3,4,5,6)
val numberIterator = numbers.iterator()
while (numberIterator.hasNext()) {
    val integer = numberIterator.next()
    if (integer < 3) {
        numberIterator.remove()
    }
}

Upvotes: 51

sadhu
sadhu

Reputation: 125

The following code works for me:

val iterator = listTotal.iterator()
for(i in iterator){
    if(i.haer== paymentsAndTagsModel.tagName){
        iterator.remove()
    }
}

You can also read this article.

Upvotes: 10

Derwrecked
Derwrecked

Reputation: 811

Use a while loop, here is the kotlin extension function:

fun <E> MutableList<E>.removeIfMatch(isMatchConsumer: (existingItem: E) -> Boolean) {
    var index = 0
    var lastIndex = this.size -1

    while(index <= lastIndex && lastIndex >= 0){
        when {
            isMatchConsumer.invoke(this[index]) -> {
                this.removeAt(index)
                lastIndex-- // max is decreased by 1
            }
            else -> index++ // only increment if we do not remove
        }
    }
}

Upvotes: 0

Mustafa G&#252;ven
Mustafa G&#252;ven

Reputation: 15744

use removeAll

pushList?.removeAll {  TimeUnit.MILLISECONDS.toMinutes(
      System.currentTimeMillis() - it.date) > THRESHOLD }

Upvotes: 37

Satej S
Satej S

Reputation: 2156

The answer by miensol seems perfect.

However, I don't understand the context for using the withIndex function or filteredIndex. You can use the filter function just by itself.

You don't need access to the index the list is at, if you're using lists.

Also, I'd strongly recommend working with a data class if you already aren't. Your code would look something like this

Data Class

data class Event(
        var eventCode : String,
        var header : String
)

Filtering Logic

fun main(args:Array<String>){

    val eventList : MutableList<Event> = mutableListOf(
            Event(eventCode = "123",header = "One"),
            Event(eventCode = "456",header = "Two"),
            Event(eventCode = "789",header = "Three")
    )


    val filteredList = eventList.filter { !it.header.equals("Two") }

}

Upvotes: 24

miensol
miensol

Reputation: 41608

It's forbidden to modify a collection through its interface while iterating over it. The only way to mutate the collection contents is to use Iterator.remove.

However using Iterators can be unwieldy and in vast majority of cases it's better to treat the collections as immutable which Kotlin encourages. You can use a filter to create a new collections like so:

listTotal = listTotal.filterIndexed { ix, element ->
    ix != 0 && ix != listTotal.lastIndex && element.header == paymentsAndTagsModel.tagName
}

Upvotes: 25

Related Questions