Remove Element from Collection During Iteration with Foreach

Remove element from collection during iteration with forEach

This is indeed expected behaviour – and is due to the fact that an Array in Swift (as well as many other collections in the standard library) is a value type with copy-on-write semantics. This means that its underlying buffer (which is stored indirectly) will be copied upon being mutated (and, as an optimisation, only when it's not uniquely referenced).

When you come to iterate over a Sequence (such as an array), be it with forEach(_:) or a standard for in loop, an iterator is created from the sequence's makeIterator() method, and its next() method is repeatedly applied in order to sequentially generate elements.

You can think of iterating over a sequence as looking like this:

let sequence = [1, 2, 3, 4]
var iterator = sequence.makeIterator()

// `next()` will return the next element, or `nil` if
// it has reached the end sequence.
while let element = iterator.next() {
// do something with the element
}

In the case of Array, an IndexingIterator is used as its iterator – which will iterate through the elements of a given collection by simply storing that collection along with the current index of the iteration. Each time next() is called, the base collection is subscripted with the index, which is then incremented, until it reaches endIndex (you can see its exact implementation here).

Therefore, when you come to mutate your array in the loop, its underlying buffer is not uniquely referenced, as the iterator also has a view onto it. This forces a copy of the buffer – which myCollection then uses.

So, there are now two arrays – the one which is being iterated over, and the one you're mutating. Any further mutations in the loop won't trigger another copy, as long as myCollection's buffer remains uniquely referenced.

This therefore means that it's perfectly safe to mutate a collection with value semantics while enumerating over it. The enumeration will iterate over the full length of the collection – completely independant of any mutations you do, as they will be done on a copy.

Remove elements from collection while iterating

Let me give a few examples with some alternatives to avoid a ConcurrentModificationException.

Suppose we have the following collection of books

List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));

Collect and Remove

The first technique consists in collecting all the objects that we want to delete (e.g. using an enhanced for loop) and after we finish iterating, we remove all found objects.

ISBN isbn = new ISBN("0-201-63361-2");
List<Book> found = new ArrayList<Book>();
for(Book book : books){
if(book.getIsbn().equals(isbn)){
found.add(book);
}
}
books.removeAll(found);

This is supposing that the operation you want to do is "delete".

If you want to "add" this approach would also work, but I would assume you would iterate over a different collection to determine what elements you want to add to a second collection and then issue an addAll method at the end.

Using ListIterator

If you are working with lists, another technique consists in using a ListIterator which has support for removal and addition of items during the iteration itself.

ListIterator<Book> iter = books.listIterator();
while(iter.hasNext()){
if(iter.next().getIsbn().equals(isbn)){
iter.remove();
}
}

Again, I used the "remove" method in the example above which is what your question seemed to imply, but you may also use its add method to add new elements during iteration.

Using JDK >= 8

For those working with Java 8 or superior versions, there are a couple of other techniques you could use to take advantage of it.

You could use the new removeIf method in the Collection base class:

ISBN other = new ISBN("0-201-63361-2");
books.removeIf(b -> b.getIsbn().equals(other));

Or use the new stream API:

ISBN other = new ISBN("0-201-63361-2");
List<Book> filtered = books.stream()
.filter(b -> b.getIsbn().equals(other))
.collect(Collectors.toList());

In this last case, to filter elements out of a collection, you reassign the original reference to the filtered collection (i.e. books = filtered) or used the filtered collection to removeAll the found elements from the original collection (i.e. books.removeAll(filtered)).

Use Sublist or Subset

There are other alternatives as well. If the list is sorted, and you want to remove consecutive elements you can create a sublist and then clear it:

books.subList(0,5).clear();

Since the sublist is backed by the original list this would be an efficient way of removing this subcollection of elements.

Something similar could be achieved with sorted sets using NavigableSet.subSet method, or any of the slicing methods offered there.

Considerations:

What method you use might depend on what you are intending to do

  • The collect and removeAl technique works with any Collection (Collection, List, Set, etc).
  • The ListIterator technique obviously only works with lists, provided that their given ListIterator implementation offers support for add and remove operations.
  • The Iterator approach would work with any type of collection, but it only supports remove operations.
  • With the ListIterator/Iterator approach the obvious advantage is not having to copy anything since we remove as we iterate. So, this is very efficient.
  • The JDK 8 streams example don't actually removed anything, but looked for the desired elements, and then we replaced the original collection reference with the new one, and let the old one be garbage collected. So, we iterate only once over the collection and that would be efficient.
  • In the collect and removeAll approach the disadvantage is that we have to iterate twice. First we iterate in the foor-loop looking for an object that matches our removal criteria, and once we have found it, we ask to remove it from the original collection, which would imply a second iteration work to look for this item in order to remove it.
  • I think it is worth mentioning that the remove method of the Iterator interface is marked as "optional" in Javadocs, which means that there could be Iterator implementations that throw UnsupportedOperationException if we invoke the remove method. As such, I'd say this approach is less safe than others if we cannot guarantee the iterator support for removal of elements.

How to remove elements from a generic list while iterating over it?

Iterate your list in reverse with a for loop:

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
// some code
// safePendingList.RemoveAt(i);
}

Example:

var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i] > 5)
list.RemoveAt(i);
}
list.ForEach(i => Console.WriteLine(i));

Alternately, you can use the RemoveAll method with a predicate to test against:

safePendingList.RemoveAll(item => item.Value == someValue);

Here's a simplified example to demonstrate:

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));

Calling remove in foreach loop in Java

To safely remove from a collection while iterating over it you should use an Iterator.

For example:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
String s = i.next(); // must be called before you can call i.remove()
// Do something
i.remove();
}

From the Java Documentation :

The iterators returned by this class's iterator and listIterator
methods are fail-fast: if the list is structurally modified at any
time after the iterator is created, in any way except through the
iterator's own remove or add methods, the iterator will throw a
ConcurrentModificationException. Thus, in the face of concurrent
modification, the iterator fails quickly and cleanly, rather than
risking arbitrary, non-deterministic behavior at an undetermined time
in the future.

Perhaps what is unclear to many novices is the fact that iterating over a list using the for/foreach constructs implicitly creates an iterator which is necessarily inaccessible. This info can be found here

Is it safe to delete elements in a Set while iterating with for..of?

Yes, it is perfectly fine to add elements and remove elements to a set while iterating it. This use case was considered and is supported in JavaScript 2015 (ES6). It will leave it in a consistent state. Note this also applies to itearting with forEach.

Intuitively:

The set iteration algorithm basically looks something like this:

Set position to 0
While position < calculateLength() // note it's calculated on each iteration
return the element at set.entryList[position]

Addition just looks something like this:

If element not in set
Add element to the _end_ of the set

So it does not interfere with existing iterations - they will iterate it.

Deletion looks something like this:

Replace all elements with are equal to `element` with a special empty value

Replacing it with an empty value rather than deleting it ensures it will not mess up with iterators' positions.


Formally

Addition

Here is the relevant part of the specification from %SetIteratorPrototype%.next:

Repeat while index is less than the total number of elements of entries. The number of elements must be redetermined each time this method is evaluated.

The set iterator proceeds to iterate the entries one by one.

From Set.prototype.add:

Append value as the last element of entries.

This ensures that when adding elements to the list it will be iterated before the iteration completes since it always gets a new slot in the entries list. Thus this will work as the spec mandates.

As for deletion:

Replace the element of entries whose value is e with an element whose value is empty.

Replacing it with an empty element rather than removing it ensures that the iteration order of existing iterators will not get out or order and they will continue iterating the set correctly.

With code

Here is a short code snippet that demonstrates this ability

var set = new Set([1]);
for(let item of set){
if(item < 10) set.add(item+1);
console.log(item);
}

Which logs the numbers 1 to 10. Here is a version not using for... of you can run in your browser today:

var set = new Set([1]);for (var _i = set[Symbol.iterator](), next; !(next = _i.next()).done;) {   var item = next.value;   if (item < 10) set.add(item + 1);   document.body.innerHTML += " " + item;}

How add or remove object while iterating Collection in C#

foreach is designed for iterating over a collection without modifing it.

To remove items from a collection while iterating over it use a for loop from the end to the start of it.

for(int i = gems.Count - 1; i >=0 ; i--)
{
gems[i].Value.Update(gameTime);

if (gems[i].Value.BoundingCircle.Intersects(Player.BoundingRectangle))
{
Gem gem = gems[i];
gems.RemoveAt(i); // Assuming it's a List<Gem>
OnGemCollected(gem.Value, Player);
}
}

If it's a dictionary<string, Gem> for example, you could iterate like this:

foreach(string s in gems.Keys.ToList())
{
if(gems[s].BoundingCircle.Intersects(Player.BoundingRectangle))
{
gems.Remove(s);
}
}

Remove an element while iterating a list

You're using iterator but you iterate this list using iter. Make sure your variable names are correct.

for (Iterator<Punk> iter = list.listIterator(); iter.hasNext(); ) {
Punk p = iter.next();

if (some condition ) {
iter.remove();
}
}

How to iterate over list while removing items at the same time?

What you can do is use an ObservableCollection here so that the code that is iterating over the collection has a way of detecting when and how the collection is mutated while it is iterating. By using an ObservableCollection the iterating code can increment the index when an item is added before the current index, or decriment it when an item is removed from before the current index.

public static IEnumerable<T> IterateWhileMutating<T>(
this ObservableCollection<T> list)
{
int i = 0;
NotifyCollectionChangedEventHandler handler = (_, args) =>
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (args.NewStartingIndex <= i)
i++;
break;
case NotifyCollectionChangedAction.Move:
if (args.NewStartingIndex <= i)
i++;
if (args.OldStartingIndex <= i) //note *not* else if
i--;
break;
case NotifyCollectionChangedAction.Remove:
if (args.OldStartingIndex <= i)
i--;
break;
case NotifyCollectionChangedAction.Reset:
i = int.MaxValue;//end the sequence
break;
default:
//do nothing
break;
}
};
try
{
list.CollectionChanged += handler;
for (i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
finally
{
list.CollectionChanged -= handler;
}
}

The code is taken from this other answer of mine. It contains additional tangential information about the consequences of iterating a sequence while mutating it, as well as some additional explanation about this code and the implications of its design decisions.

Exception during iteration on collection and remove items from that collection

You can iterate over a copy of the collection:

foreach(var fullFilePath in new ArrayList(attachmentsFilePath))
{
// do stuff
}


Related Topics



Leave a reply



Submit