I have an array and I want to remove a bunch of indices
var arr = [0,1,2,3,4,5,6]
var rmIndices = [1,4,5]
What is the best way to remove indices 1,4,5 from arr?
Note that PermutationGenerator is going away in Swift 3 and also doesn't keep the ordering the same, though perhaps it did at one time. Using the accepted answer results in [2, 6, 0, 3] which may be unexpected. A couple of alternative approaches that give the expected result of [0, 2, 3, 6] are:
let flatArr = arr.enumerate().flatMap { rmIndices.contains($0.0) ? nil : $0.1 }
or
let filterArr = arr.enumerate().filter({ !rmIndices.contains($0.0) }).map { $0.1 }
let flatArr = arr.enumerated().flatMap { (index, value) in rmIndices.contains(index) ? nil : value }enumerated)Rather than a list of indices to remove, it may be easier to have a list of indices to keep, which you can do using the Set type:
let rmIndices = [1,4,5]
let keepIndices = Set(arr.indices).subtract([1,4,5])
Then you can use PermutationGenerator to create a fresh array of just those indices:
arr = Array(PermutationGenerator(elements: arr, indices: keepIndices))
rmIndices.sort({ $1 < $0 })
for index in rmIndices
{
arr.removeAtIndex(index)
}
Note that I've sorted the indices in descending order. This is because everytime you remove an element E, the indices of the elements beyond E reduce by one.
Array.removeAtIndex “Invalidates all indices with respect to self.” In practice, this is probably OK, but in theory they’re at liberty to change Swift in the future in a way that would make the results of doing this undefined.arr.removeAtIndex(index) would invalidate the indices on arr, but not on rmIndices, so the for in loop is safe. Complexity would be O(m*n), being m the number of indices to remove and n the number of items in the original array. @Vatsal, sorting the rmIndices array here wouldn't have any effect.rmIndices are absolutely effected by removing elements from arr, because they are referring to specific elements of arr. When you remove element 0 of arr, then index 1 no longer points to the same element, because every element shuffles down. Just because the numbers are not “invalidated” in the “can’t be used any more” sense doesn’t mean they work just as they did before the removal. Note it is this shuffling-down behaviour that means that, far from not having any effect, the sort is required for correct behaviour.sortInPlace instead of sort at the time of writing.For Swift 3
var arr = [0,1,2,3,4,5,6]
let rmIndices = [1,4,5]
arr = arr.filter{ !rmIndices.contains($0) }
print(arr)
if you want to produce output very fastly then you can use
var arr = [0,1,2,3,4,5,6]
let rmIndices = [1,4,5]
arr = Array(Set(arr).subtracting(rmIndices))
print(array)
But it will change order of your array
containsarr, assuming that the contents of your array are something other than the indices in order ;)Remove elements using indexes array:
Array of Strings and indexes
let animals = ["cats", "dogs", "chimps", "moose", "squarrel", "cow"]
let indexAnimals = [0, 3, 4]
let arrayRemainingAnimals = animals
.enumerated()
.filter { !indexAnimals.contains($0.offset) }
.map { $0.element }
print(arrayRemainingAnimals)
//result - ["dogs", "chimps", "cow"]
Array of Integers and indexes
var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
let indexesToRemove = [3, 5, 8, 12]
numbers = numbers
.enumerated()
.filter { !indexesToRemove.contains($0.offset) }
.map { $0.element }
print(numbers)
//result - [0, 1, 2, 4, 6, 7, 9, 10, 11]
Remove elements using element value of another array
Arrays of integers
let arrayResult = numbers.filter { element in
return !indexesToRemove.contains(element)
}
print(arrayResult)
//result - [0, 1, 2, 4, 6, 7, 9, 10, 11]
Arrays of strings
let arrayLetters = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
let arrayRemoveLetters = ["a", "e", "g", "h"]
let arrayRemainingLetters = arrayLetters.filter {
!arrayRemoveLetters.contains($0)
}
print(arrayRemainingLetters)
//result - ["b", "c", "d", "f", "i"]
map produce a new array, therefore leaving the original intact? If so then the first example doesn't really remove the elements.In Swift 4:
let newArr = arr.enumerated().compactMap {
rmIndices.contains($0.0) ? nil : $0.1
}
enumerated() generates (index, value) pairscompactMap concatenates non-nil values$0.0 is the index (first element of enumerated pair) as $0.1$ is the valuecompactMap gathers values whose indices are not found in rmIndices The problem with flatmap is that it gives incorrect results if your array contains optionals.
The following is much faster than the functional style solutions provided and works with optionals. You just have to make sure rmIndices is sorted and unique. It's also fairly language agnostic.
var numRemoved: Int = 0
for index in rmIndices {
let indexToRemove = index - numRemoved
arr.remove(at: indexToRemove)
numRemoved += 1
}
If you need to make sure rmIndices is sorted and unique:
rmIndices = Set(rmIndices).sorted()
Using XCTest to remove 500 elements (including the operation to ensure uniqueness and sorted):
0.006 sec
vs.
arr.enumerated().filter({ !rmIndices.contains($0.0) }).map { $0.1 }:
0.206 sec
I use this as an extension on Array
extension Array {
mutating func remove(at indices: [Int]) {
let rmIndices = Set(indices).sorted()
var numRemoved: Int = 0
for index in rmIndices {
let indexToRemove = index - numRemoved
self.remove(at: indexToRemove)
numRemoved += 1
}
}
}
remove(at:) all the elements following the specified position are moved to close the gap (aka removal is proportional to the length of the collection). If you use remove(at:) inside a for-loop, you're getting yourself into a O(n^2) operation. For a more comprehensive explanation watch the following video from minute 6:23 developer.apple.com/videos/play/wwdc2018/223