Reputation: 7883
I want to enumerate through an array in Swift, and remove certain items. I'm wondering if this is safe to do, and if not, how I'm supposed to achieve this.
Currently, I'd be doing this:
for (index, aString: String) in enumerate(array) {
//Some of the strings...
array.removeAtIndex(index)
}
Upvotes: 95
Views: 43849
Reputation: 12651
Since this important question only has very old answers, some of which mention the current features/syntax incompletely, here's the current answer with no cruft and a full example of the syntaxes.
Traditionally (in most languages / milieu) to delete from an array you work through it backwards.
In modern Swift do not do that, do this:
var things: [Thing]
things.removeAll{ thing in
// your code block returns Bool
print("checking \(thing.field)")
// your calculations
// your calculations
return yourBooleanResult
}
or in simpler situations
things.removeAll{ $0.length > 7.0 }
This is O(n), which is good news. It does preserve the original order and you can use it on any mutable collection.
https://developer.apple.com/documentation/swift/substring/removeall(where:)
This interesting document
https://github.com/apple/swift-evolution/blob/main/proposals/0197-remove-where.md
.. touches on the dangers and inefficiencies of different approaches to removing items from a collection, explains the shuffle down algorithm etc.
Now that this exists there is no situation where you would use "hand made" code (typically iterating backwards, etc) for the job when using Swift.
Upvotes: -2
Reputation: 20884
In Swift 2 this is quite easy using enumerate
and reverse
.
var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
a.removeAtIndex(i)
}
print(a)
Upvotes: 78
Reputation: 13281
Just to add, if you have multiple arrays and each element in index N of array A is related to the index N of array B, then you can still use the method reversing the enumerated array (like the past answers). But remember that when accessing and deleting the elements of the other arrays, no need to reverse them.
Like so, (one can copy and paste this on Playground)
var a = ["a", "b", "c", "d"]
var b = [1, 2, 3, 4]
var c = ["!", "@", "#", "$"]
// remove c, 3, #
for (index, ch) in a.enumerated().reversed() {
print("CH: \(ch). INDEX: \(index) | b: \(b[index]) | c: \(c[index])")
if ch == "c" {
a.remove(at: index)
b.remove(at: index)
c.remove(at: index)
}
}
print("-----")
print(a) // ["a", "b", "d"]
print(b) // [1, 2, 4]
print(c) // ["!", "@", "$"]
Upvotes: 0
Reputation: 490
The traditional for loop could be replaced with a simple while loop, useful if you also need to perform some other operations on each element prior to removal.
var index = array.count-1
while index >= 0 {
let element = array[index]
//any operations on element
array.remove(at: index)
index -= 1
}
Upvotes: 1
Reputation: 3847
In Swift 3 and 4, this would be:
With numbers, according to Johnston's answer:
var a = [1,2,3,4,5,6]
for (i,num) in a.enumerated().reversed() {
a.remove(at: i)
}
print(a)
With strings as the OP's question:
var b = ["a", "b", "c", "d", "e", "f"]
for (i,str) in b.enumerated().reversed()
{
if str == "c"
{
b.remove(at: i)
}
}
print(b)
However, now in Swift 4.2 or later, there is even a better, faster way that was recommended by Apple in WWDC2018:
var c = ["a", "b", "c", "d", "e", "f"]
c.removeAll(where: {$0 == "c"})
print(c)
This new way has several advantages:
filter
.Upvotes: 46
Reputation: 6726
You might consider filter
way:
var theStrings = ["foo", "bar", "zxy"]
// Filter only strings that begins with "b"
theStrings = theStrings.filter { $0.hasPrefix("b") }
The parameter of filter
is just a closure that takes an array type instance (in this case String
) and returns a Bool
. When the result is true
it keeps the element, otherwise the element is filtered out.
Upvotes: 58
Reputation: 72760
When an element at a certain index is removed from an array, all subsequent elements will have their position (and index) changed, because they shift back by one position.
So the best way is to navigate the array in reverse order - and in this case I suggest using a traditional for loop:
for var index = array.count - 1; index >= 0; --index {
if condition {
array.removeAtIndex(index)
}
}
However in my opinion the best approach is by using the filter
method, as described by @perlfly in his answer.
Upvotes: 14
Reputation: 119031
Either create a mutable array to store the items to be deleted and then, after the enumeration, remove those items from the original. Or, create a copy of the array (immutable), enumerate that and remove the objects (not by index) from the original while enumerating.
Upvotes: 2
Reputation: 140
I recommend to set elements to nil during enumeration, and after completing remove all empty elements using arrays filter() method.
Upvotes: 0
Reputation: 1148
No it's not safe to mutate arrays during enumaration, your code will crash.
If you want to delete only a few objects you can use the filter
function.
Upvotes: 5