修改参数值时使用 filter{where:} 与 removeAll{where:} 的效率

Efficiency of using filter{where:} vs. removeAll{where:} when modifying a parameter value

Swift 4.2引入了一个新的removeAll {where:} function。据我所知,它应该比使用过滤器 {where:} 更有效。我的代码中有几个这样的场景:

private func getListOfNullDates(list: [MyObject]) -> [MyObject] {
        return list.filter{ [=10=].date == nil }
            .sorted { [=10=].account?.name < .account?.name }
    }

但是,我不能将 removeAll{where:} 与参数一起使用,因为它是一个常量。所以我需要像这样重新定义它:

private func getListOfNullDates(list: [MyObject]) -> [MyObject] {
        var list = list
        list.removeAll { [=11=].date == nil }
        return list.sorted { [=11=].account?.name < .account?.name }
    }

在这种情况下使用 removeAll 函数是否仍然更有效?还是坚持使用过滤功能更好?

谨防过早优化。一种方法的效率通常取决于您的特定数据和配置,除非您正在处理大型数据集或同时执行许多操作,否则这两种方式都不太可能产生重大影响。除非确实如此,否则您应该支持更具可读性和可维护性的解决方案。

作为一般规则,当您想要改变原始数组时只需使用 removeAll,如果您不想改变则使用 filter。如果您已将其确定为程序中的潜在瓶颈,然后对其进行测试以查看是否存在性能差异。

谢谢你提出这个问题

我已经 benchmarked 在 TIO 上使用此代码的两个函数:

let array = Array(0..<10_000_000)

do {
    let start = Date()
    let filtering = array.filter { [=10=] % 2 == 0 }
    let end = Date()

    print(filtering.count, filtering.last!, end.timeIntervalSince(start))
}

do {
    let start = Date()
    var removing = array
    removing.removeAll { [=10=] % 2 == 0 }
    let end = Date()

    print(removing.count, removing.last!, end.timeIntervalSince(start))
}

(要使 removingfiltering 相同,传递给 removeAll 的闭包应该是 { [=16=] % 2 != 0 },但我不想提供优势通过使用更快或更慢的比较运算符来选择任一片段。)

确实,当删除元素的概率(我们称之为 Pr)为 50% 时,removeAll(where:) 更快!以下是基准测试结果:

filter    : 94ms
removeAll : 74ms

Pris less than 50%.

也是如此

否则,过滤 is faster 以获得更高的 Pr

要记住的一件事是,在您的代码中,list 是可变的,这会导致意外修改的可能性。

就个人而言,我会选择性能而不是旧习惯,从某种意义上说,这个用例由于意图更明确而更具可读性。


奖励:就地删除

删除就地的意思是交换数组中的元素,使要删除的元素位于某个主元索引之后。要保留的元素是枢轴元素之前的元素:

var inPlace = array
let p = inPlace.partition { [=12=] % 2 == 0 } 

请记住,partition(by:) 不会保留原始顺序。

这种方法 clocks 优于 removeAll(where:)