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 givenListIterator
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 beIterator
implementations that throwUnsupportedOperationException
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
How to Save Data from Cloud Firestore to a Variable in Swift
Array Element Cannot Be Bridged to Objective-C
How to Unittest Combine Cancellables
Compiler Error When Comparing Values of Enum Type with Associated Values
Access Input from Uialertcontroller
Swift Generics: Requiring Addition and Multiplication Abilities of a Type
Build Error When Trying to Override an Initializer in Xcode 6.3 Beta 3
How Does One Trap Arithmetic Overflow Errors in Swift
Transparent Background for Texteditor in Swiftui
How to Set a New Root View Controller
Try, Try! & Try? What's the Difference, and When to Use Each
Swift 5: What's 'Escaping Closure Captures Mutating 'Self' Parameter' and How to Fix It
Println Dictionary Has "Optional"
In Xcode 6.1. 'Uiimage' Does Not Have a Member Named 'Size' Error
Hide Navigation Bar Without Losing Swipe Back Gesture in Swiftui
How to Convert Timeinterval into Minutes, Seconds and Milliseconds in Swift