What is a Swift array equivalent to NSMutableArray
's -removeObjectsAtIndexes:
? Removing each index one by one doesn't work, as remaining indexes will shift after removing one index. What's an efficient way to implement this functionality?
14 Answers
According to WWDC 2018 Session 223 – Embracing Algorithms an efficient solution is the half stable partition algorithm:
extension RangeReplaceableCollection where Self: MutableCollection, Index == Int {
mutating func remove(at indexes : IndexSet) {
guard var i = indexes.first, i < count else { return }
var j = index(after: i)
var k = indexes.integerGreaterThan(i) ?? endIndex
while j != endIndex {
if k != j { swapAt(i, j); formIndex(after: &i) }
else { k = indexes.integerGreaterThan(k) ?? endIndex }
formIndex(after: &j)
}
removeSubrange(i...)
}
}
It moves all elements which are not in the index set to the end of the array just by swapping the elements. Half stable means the algorithm preserves the order of the left partition but doesn't care about the order of the right side as the elements will be removed anyway. After the loop the variable i
contains the first index of the items to be removed. The batch remove operation is inexpensive because no indexes will be shifted/rebuilt.
For example if you have an array
[0, 1, 2, 3, 4, 5, 6, 7]
and you want to remove the elements at index 2
and 4
the algorithm performs the following steps in the while
loop (the initial value of the index j
is the index after the first index to be removed):
- Index
3
: Swap elements at index2
and3
→[0, 1, 3, 2, 4, 5, 6, 7]
- Index
4
: No change - Index
5
: Swap elements at index3
and5
→[0, 1, 3, 5, 4, 2, 6, 7]
- Index
6
: Swap elements at index4
and6
→[0, 1, 3, 5, 6, 2, 4, 7]
Index
7
: Swap elements at index5
and7
→[0, 1, 3, 5, 6, 7, 4, 2]
Finally remove the elements at subrange
6...

- 274,689
- 30
- 353
- 361
-
1I thought of something like that, but then I got worried about repeatedly saying `indexes.contains(j)`. – matt Aug 09 '18 at 03:12
-
@matt I suppose `contains` is optimized at its best and in most cases `indexes` is not a very large list. And swapping the elements is much more efficient than rebuilding the indexes. – vadian Aug 09 '18 at 08:15
-
The point of the video is scalability. What if the array is huge and the index set is huge? Then for every element of the array you are calling `contains` on the index set. That’s _not_ what happens in the video! He walks the array asking each element `isSelected`, which is simple and fast. Do you see? – matt Aug 09 '18 at 11:42
-
Yes I see, but isn’t `contains` in an index**Set** O(n)? – vadian Aug 09 '18 at 12:37
-
I don't know. That's my point. I'm asking _you_. I was about to post a question asking about that — and that's how I discovered your answer here. But you'd better hope not, because if it is, then your algorithm is O(n2) and your answer would be no improvement over mine (`reversed()` and `remove(at:)`). Your answer cites the Embracing Algorithms video, but it might not be parallel to the video at all, insofar as it pushes back the key point of uncertainty into `contains`. – matt Aug 09 '18 at 12:54
-
Let me put it another way. The video says that `removeAll` uses half stable partition. So your answer is really no different from using `removeAll`. Call our array `arr` and our index set `set`. Then your answer is effectively identical to `var arr2 = Array(arr.enumerated()); arr2.removeAll{set.contains($0.offset)}; arr = arr2.map{$0.element}`. That expression of it throws a lot less dust in the eyes of the beholder, and forces us to ask ourselves: What about that `contains`? – matt Aug 09 '18 at 13:00
-
`extension RangeReplaceableCollection where Self: MutableCollection, Index == Int {` – Leo Dabus Oct 18 '18 at 13:03
-
4It would be awesome if this was added directly to the Swift standard library! – Robin Stewart Jun 08 '19 at 16:37
I like a pure Swift solution, i.e. without resorting to NSIndexSet:
extension Array {
mutating func removeAtIndexes (ixs:[Int]) -> () {
for i in ixs.sorted(>) {
self.removeAtIndex(i)
}
}
}
EDIT In Swift 4 that would be:
extension Array {
mutating func remove (at ixs:[Int]) -> () {
for i in ixs.sorted(by: >) {
self.remove(at:i)
}
}
}
But years after I wrote that answer, the WWDC 2018 Embracing Algorithms video points out the flaw: it's O(n2), because remove(at:)
itself has to loop through the array.
According to that video, Swift 4.2 removeAll(where:)
is efficient because it uses half-stable partitioning. So we could write something like this:
extension Array {
mutating func remove(at set:IndexSet) {
var arr = Swift.Array(self.enumerated())
arr.removeAll{set.contains($0.offset)}
self = arr.map{$0.element}
}
}
My tests show that despite the repeated contains
, that's 100 times faster. However, @vadian's approach is 10 times faster than that, because he unrolls contains
by ingeniously walking the index set at the same time he walks the array (using half-stable partitioning).

- 515,959
- 87
- 875
- 1,141
-
FYI: I [rewrote the code](https://pastebin.com/HLEVUvY8) in my deleted answer to work around the O(n2) problem by using an extra variable for the current `IndexSet` index and I really expected that this version is much faster than descending sort / remove, but it isn't. It's twice as slow. So this swapping thing is yet more expensive than the WWDC video suggested. – vadian Aug 10 '18 at 07:32
-
@vadian I suspect you've got a Heisenbug. (You showed your algorithm but you didn't show your testing procedure.) I measured my two methods against each other and when the first took 25 seconds, the second took 0.2 seconds — two orders of magnitude. I'll post my test code on github. – matt Aug 10 '18 at 14:57
-
@vadian I tested my first method against your algorithm on pastebin and yours was _three_ orders of magnitude faster! I've little doubt that this is because you've avoided the repeated `contains`. Using `contains` wasn't as bad as I feared it would be, but your rewrite is vastly better. So I think you should repost your answer with that as your code. I'll upvote it! – matt Aug 10 '18 at 15:02
-
I added my primitive test environment on pastebin. Same link. Maybe this kind of testing is not very meaningful. – vadian Aug 10 '18 at 15:14
-
@vadian Your testing procedure is pretty flawed. For example, you are including the time needed to _create_ the array in your time totals. And you're not exercising the algorithms thoroughly enough to be very meaningful; you are not creating a "worst case scenario", which is what's needed. Here's mine: https://github.com/mattneub/RemoveTest Download it and give it a shot. (Run on _device_, using _release_ build. Otherwise the timing is meaningless.) Please look also to see if you can find any flaws in my test procedure. – matt Aug 10 '18 at 15:23
-
I know. Yours is much more professional. Thanks for sharing. I can't see any flaws. I'm just downloading Xcode 10 to be able to run it – vadian Aug 10 '18 at 16:12
-
Right, sorry, `removeAll(where:)` is Swift 4.2 only, so you'll need Xcode 10 beta. – matt Aug 10 '18 at 16:19
-
Wow, I didn't know that there are such striking differences between debug and release build – vadian Aug 10 '18 at 16:31
-
@vadian Debug build is heavily deoptimized! You can’t measure anything with it. Timings, memory usage, it all works totally differently, especially in Swift. – matt Aug 10 '18 at 17:07
Updated for Swift 2.0:
extension Array {
mutating func removeAtIndices(incs: [Int]) {
incs.sort(>).map { removeAtIndex($0) }
}
}
Use forEach
instead of map
if it gives a warning that the result isn't used (Since Swift 2 beta 6 I think)
EDIT: Super generic lazy solution:
extension RangeReplaceableCollectionType where Index : Comparable {
mutating func removeAtIndices<S : SequenceType where S.Generator.Element == Index>(indices: S) {
indices.sort().lazy.reverse().forEach{ removeAtIndex($0) }
}
}

- 14,673
- 7
- 45
- 62
I ended up doing it this way:
According to Apple's documentation on NSIndexSet
, "index sets store indexes as sorted ranges". So we could enumerate over the given NSIndexSet
backwards and remove the element from the array at each index one by one, like so:
extension Array {
mutating func removeAtIndexes(indexes: NSIndexSet) {
for var i = indexes.lastIndex; i != NSNotFound; i = indexes.indexLessThanIndex(i) {
self.removeAtIndex(i)
}
}
}

- 18,584
- 15
- 51
- 72
Another expansion on Ethan's answer (trimmed down from what I have in my own framework):
extension Array {
// Code taken from https://stackoverflow.com/a/26174259/1975001
// Further adapted to work with Swift 2.2
/// Removes objects at indexes that are in the specified `NSIndexSet`.
/// - parameter indexes: the index set containing the indexes of objects that will be removed
public mutating func removeAtIndexes(indexes: NSIndexSet) {
for i in indexes.reverse() {
self.removeAtIndex(i)
}
}
}
This version uses NSIndexSet
's SequenceType
extension provided by Swift's Foundation bridge.
edit: Also, newer versions of Swift (Don't remember which one added it) have a struct
type called IndexSet
, which can be bridged to/from NSIndexSet
. And can be reverse()
d like NSIndexSet
.

- 2,981
- 24
- 27
To complete Ethan's answer, C-loop styles will be deprecated in Swift 3.0. Here's then a Swift 3.0 compatible answer:
mutating func removeAtIndexes(indexes: NSIndexSet) {
var i = indexes.lastIndex
while i != NSNotFound {
self.removeAtIndex(i)
i = indexes.indexLessThanIndex(i)
}
}
-
What if `indexes` is an empty set? I'd think putting the `while i != NSNotFound` in place of the `repeat` would be more fault-tolerant. – MaddTheSane Apr 13 '16 at 07:41
I needed this for working with a NSTableview. This is what I use.
extension Array{
mutating func removeElementsAtIndexes(indexset:NSIndexSet){
self = self.enumerate().filter({!indexset.containsIndex($0.index)}).map({$0.element})
}
}

- 2,343
- 13
- 21
Swift 4 attempt
extension Array {
mutating func removeAtIndexes(indexes: IndexSet) {
var i:Index? = indexes.last
while i != nil {
self.remove(at: i!)
i = indexes.integerLessThan(i!)
}
}
}

- 2,739
- 4
- 29
- 57
I used Swift's filter
function:
func removeMusicListAtIndexes(idxSet: NSIndexSet) {
if idxSet.containsIndex(selectedMusic) {
selectedMusic = -1;
}
musicList = musicList.filter({
var idx = find(self.musicList, $0)
// If a value isn't in the index, it isn't being removed
return !idxSet.containsIndex(idx!)
})
}

- 2,981
- 24
- 27
Based upon Kent's solution but updated for Swift 3
extension Array {
mutating func remove(indices: IndexSet) {
self = self.enumerated().filter { !indices.contains($0.offset) }.map { $0.element }
}
}

- 8,596
- 2
- 39
- 48
I have not tested this, but I would bet that a non-mutating version is faster if it copies contiguous subranges. Something like:
extension Array {
public func removing(indices indicesToRemove: IndexSet) -> Self {
guard let lastIndex = indicesToRemove.last else {
return self
}
assert(lastIndex <= (self.count - 1), message: "Index set contains out of bounds indices")
var result = Self()
result.reserveCapacity(self.count - indicesToRemove.count)
let indicesToKeep = IndexSet(self.indices).subtracting(indicesToRemove)
let rangesToKeep = indicesToKeep.rangeView
rangesToKeep.forEach { (range) in
result.append(contentsOf: self[range])
}
return result
}
}

- 760
- 6
- 7
one way is:
var arrayB = arrayA.removeAtIndex(5)
Another way is:
var arr = ["I", "Love", "Life"]
let slice = arr[1..<2]
println(slice)
//[Love]

- 19,348
- 7
- 46
- 53
-
-
from Apple "“Any gaps in an array are closed when an item is removed” – Steve Rosenberg Oct 03 '14 at 05:48
Here is the solution I currently use:
extension Array {
mutating func removeObjectAtIndexes(indexes: [Int]) {
var indexSet = NSMutableIndexSet()
for index in indexes {
indexSet.addIndex(index)
}
indexSet.enumerateIndexesWithOptions(.Reverse) {
self.removeAtIndex($0.0)
return
}
}
mutating func removeObjectAtIndexes(indexes: Int...) {
removeObjectAtIndexes(indexes)
}
}

- 27,065
- 8
- 76
- 78