RemoveObjectsAtIndexes for Swift Arrays

What is a Swift array equivalent to NSMutableArray -removeObjectsAtIndexes: :? Deleting each index one by one does not work, as the remaining indexes will shift after deleting one index. What is an effective way to implement this functionality?

+12
swift
source share
13 answers

Here is the solution I'm using now:

 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) } } 
-4
source share

I like the pure Swift solution, i.e. without accessing NSIndexSet:

 extension Array { mutating func removeAtIndexes (ixs:[Int]) -> () { for i in ixs.sorted(>) { self.removeAtIndex(i) } } } 

EDIT In Swift 4 it will be:

 extension Array { mutating func remove (at ixs:[Int]) -> () { for i in ixs.sorted(by: >) { self.remove(at:i) } } } 

But years after I wrote this answer, a video about using WWDC algorithms in 2018 indicates a disadvantage: this is O (n 2 ), because the remove(at:) command itself must go through the array.

According to this video, Swift 4.2 removeAll(where:) efficient because it uses semi-stable splitting. 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 contains repeatedly, which is 100 times faster. However, the @vadian approach is 10 times faster than the one because it deploys contains , brilliantly strolling through the index set at the same time it was walking around the array (using semi-stable partitioning).

+17
source share

In accordance with session 223 of WWDC 2018 “Algorithm Coverage”, an effective solution is the semistable partitioning 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 the elements that are not in the index set to the end of the array, just changing the elements. Semistable means that the algorithm preserves the order of the left side, but does not care about the order of the right side, since the elements will be deleted in any case. After the loop, the variable i contains the first index of deleted items. The batch delete 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 elements with index 2 and 4 algorithm performs the following actions in while loops (the initial value of index j is the index after the first index has to be deleted):

  • Index 3 : Swap elements with numbers 2 and 3[0, 1, 3, 2, 4, 5, 6, 7]
  • Index 4 : Unchanged
  • Index 5 : Swap elements with numbers 3 and 5[0, 1, 3, 5, 4, 2, 6, 7]
  • Index 6 : Swap elements with numbers 4 and 6[0, 1, 3, 5, 6, 2, 4, 7]
  • Index 7 : swap elements with indices 5 and 7[0, 1, 3, 5, 6, 7, 4, 2]

  • Finally delete items in subrange 6...


+13
source share

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 is not used (since Swift 2 beta 6 I think)

EDIT: A super general 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) } } } 
+6
source share

I ended up doing it like this:

According to Apple's NSIndexSet , "an index sets store indexes as sorted ranges." Thus, we can enumerate the specified NSIndexSet opposite direction and remove the element from the array at each index one by one, for example, like this:

 extension Array { mutating func removeAtIndexes(indexes: NSIndexSet) { for var i = indexes.lastIndex; i != NSNotFound; i = indexes.indexLessThanIndex(i) { self.removeAtIndex(i) } } } 
+5
source share

Another addition to Ethan's answer (stripped down from what I have in my own structure):

 extension Array { // Code taken from /questions/706944/removeobjectsatindexes-for-swift-arrays/2701048#2701048 // 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 the NSIndexSet SequenceType extension provided by the Swift Foundation Bridge.

edit: In addition, newer versions of Swift (I don’t remember which one added it) have a struct type called IndexSet , which can be connected to NSIndexSet . And can be reverse() like NSIndexSet .

+4
source share

To complete Ethan's Response , C-loop styles will be deprecated in Swift 3.0. Here is the Swift 3.0 answer:

 mutating func removeAtIndexes(indexes: NSIndexSet) { var i = indexes.lastIndex while i != NSNotFound { self.removeAtIndex(i) i = indexes.indexLessThanIndex(i) } } 
+2
source share

I used the Swift 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!) }) } 
+1
source share

I need this to work with NSTableview. This is what I use.

 extension Array{ mutating func removeElementsAtIndexes(indexset:NSIndexSet){ self = self.enumerate().filter({!indexset.containsIndex($0.index)}).map({$0.element}) } } 
+1
source share

Based on Kent solution, but updated for Swift 3

 extension Array { mutating func remove(indices: IndexSet) { self = self.enumerated().filter { !indices.contains($0.offset) }.map { $0.element } } } 
+1
source share

Quick try 4

 extension Array { mutating func removeAtIndexes(indexes: IndexSet) { var i:Index? = indexes.last while i != nil { self.remove(at: i!) i = indexes.integerLessThan(i!) } } } 
+1
source share

one way:

 var arrayB = arrayA.removeAtIndex(5) 

Another way:

 var arr = ["I", "Love", "Life"] let slice = arr[1..<2] println(slice) //[Love] 
0
source share

I found the system API, but it is available in SwiftUI for iOS 13+.

enter image description here

0
source share

All Articles