Strange Behaviour of IndexSet

With this code:

let maxIndex = 4
var indices = IndexSet(integersIn: 0 ... maxIndex)
var curIndex = indices.startIndex
while( curIndex < indices.endIndex)
{
	let curPrix = indices[curIndex]
	print("index \(curPrix); will remove 1, 3")
	indices.remove(1) 
	indices.remove(3) 
	indices.formIndex(after: &curIndex)
}

I would expect to never see indices 1, 3. But I get:

index 0; will remove 1, 3
index 1; will remove 1, 3
index 2; will remove 1, 3
index 3; will remove 1, 3
index 4; will remove 1, 3

Is this the expected behaviour?
If yes, how is this to be understood?

Gerriet.

Calling indices.remove invalidates indices to elements of the collection such as indices.startIndex stored in curIndex. You can't rely on those indices to give you meaningful results after a mutation of the collection.

Some collection types have documented situations where indices are preserved after a mutation, but I don't see anything to that effect in IndexSet.

As for a solution, you could keep the elements to remove in a separate collection and only remove them at the end:

let maxIndex = 4
var indices = IndexSet(integersIn: 0 ... maxIndex)
var curIndex = indices.startIndex
var toRemove = IndexSet()
while( curIndex < indices.endIndex)
{
	let curPrix = indices[curIndex]
	// don't act on elements scheduled to be removed
	if !toRemove.contains(curPrix) {
		print("index \(curPrix); will remove 1, 3")
		toRemove.insert(1)
		toRemove.insert(3)
	}
	indices.formIndex(after: &curIndex)
}
// finally removing what needs to be removed
indices.subtract(toRemove)

Another solution would be to use a for loop where you check if elements were previously removed:

let maxIndex = 4
var indices = IndexSet(integersIn: 0 ... maxIndex)
for curPrix in indices {
	if indices.contains(curPrix) {
		print("index \(curPrix); will remove 1, 3")
		indices.remove(1)
		indices.remove(3)
	} else {
		// ignore: element already removed from indices
	}
}
1 Like