Swift 4.2 introduced a new removeAll {where:} function. From what I have read, it is supposed to be more efficient than using filter {where:}. I have several scenarios in my code like this:
private func getListOfNullDates(list: [MyObject]) -> [MyObject] {
return list.filter{ $0.date == nil }
.sorted { $0.account?.name < $1.account?.name }
}
However, I cannot use removeAll{where:} with a param because it is a constant. So I would need to redefine it like this:
private func getListOfNullDates(list: [MyObject]) -> [MyObject] {
var list = list
list.removeAll { $0.date == nil }
return list.sorted { $0.account?.name < $1.account?.name }
}
Is using the removeAll function still more efficient in this scenario? Or is it better to stick with using the filter function?
Thank you for this question 🙏🏻
I've benchmarked both functions using this code on TIO:
let array = Array(0..<10_000_000)
do {
let start = Date()
let filtering = array.filter { $0 % 2 == 0 }
let end = Date()
print(filtering.count, filtering.last!, end.timeIntervalSince(start))
}
do {
let start = Date()
var removing = array
removing.removeAll { $0 % 2 == 0 }
let end = Date()
print(removing.count, removing.last!, end.timeIntervalSince(start))
}
(To have the removing
and filtering
identical, the closure passed to removeAll
should have been { $0 % 2 != 0 }
, but I didn't want to give an advantage to either snippet by using a faster or slower comparison operator.)
And indeed, removeAll(where:)
is faster when the probability of removing elements (let's call it Pr
)is 50%! Here are the benchmark results :
filter : 94ms
removeAll : 74ms
This is the same case when Pr
is less than 50%.
Otherwise, filtering is faster for a higher Pr
.
One thing to bear in mind is that in your code list
is mutable, and that opens the possibility for accidental modifications.
Personally, I would choose performance over old habits, and in a sense, this use case is more readable since the intention is clearer.
What's meant by removing in-place is the swap the elements in the array in such a way that the elements to be removed are placed after a certain pivot index. The elements to keep are the ones before the pivot element :
var inPlace = array
let p = inPlace.partition { $0 % 2 == 0 }
Bear in mind that partition(by:)
doesn't keep the original order.
This approach clocks better than removeAll(where:)
Beware of premature optimization. The efficiency of a method often depends on your specific data and configuration, and unless you're working with a large data set or performing many operations at once, it's not likely to have a significant impact either way. Unless it does, you should favor the more readable and maintainable solution.
As a general rule, just use removeAll
when you want to mutate the original array and filter
when you don't. If you've identified it as a potential bottleneck in your program, then test it to see if there's a performance difference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With