为什么以及何时对 Swift 中的数组使用惰性?

Why and when to use lazy with Array in Swift?

[1, 2, 3, -1, -2].filter({ [=11=] > 0 }).count // => 3

[1, 2, 3, -1, -2].lazy.filter({ [=11=] > 0 }).count // => 3

在第二个语句中添加lazy有什么好处。根据我的理解,当使用 lazy 变量时,内存在使用时被初始化为该变量。在这种情况下它有什么意义?

尝试更详细地了解 LazySequence 的用法。我曾在序列上使用过 mapreducefilter 函数,但从未在 lazy 序列上使用过。需要了解为什么要使用这个?

我以前没见过这个,所以我做了一些搜索并找到了它。

您 post 的语法创建了一个惰性集合。惰性集合避免为代码的每个步骤创建一整系列的中间数组。当你只有一个过滤器语句时它并不重要,如果你做类似 filter.map.map.filter.map 的事情会更有效,因为如果没有惰性集合,每一步都会创建一个新数组。

有关详细信息,请参阅本文:

https://medium.com/developermind/lightning-read-1-lazy-collections-in-swift-fa997564c1a3

编辑:

我做了一些基准测试,一系列高阶函数(如映射和过滤器)实际上在惰性集合上比在“常规”集合上慢一点。

看起来惰性集合以稍慢的性能为代价为您提供了更小的内存占用。

编辑#2:

@discardableResult func timeTest() -> Double {
    let start = Date()
    let array = 1...1000000
    let random = array
        .map { (value) -> UInt32 in
            let random = arc4random_uniform(100)
            //print("Mapping", value, "to random val \(random)")
            return random
    }
    let result = random.lazy  //Remove the .lazy here to compare
        .filter {
            let result = [=10=] % 100 == 0
            //print("  Testing \([=10=]) < 50", result)
            return result
        }
        .map { (val: UInt32) -> NSNumber in
            //print("    Mapping", val, "to NSNumber")
            return NSNumber(value: val)
        }
        .compactMap { (number) -> String? in
            //print("      Mapping", number, "to String")
            return formatter.string(from: number)
        }
        .sorted { (lhv, rhv) -> Bool in
            //print("        Sorting strings")
            return (lhv.compare(rhv, options: .numeric) == .orderedAscending)
    }
    
    let elapsed = Date().timeIntervalSince(start)
    
    print("Completed in", String(format: "%0.3f", elapsed), "seconds. count = \(result.count)")
    return elapsed
}

在上面的代码中,如果你改变行

let result = random.lazy  //Remove the .lazy here to compare

let result = random  //Removes the .lazy here

然后它运行得更快。对于 lazy,我的基准测试表明,与直接数组相比,.lazy 集合花费的时间大约长 1.5 倍。

lazy 改变数组的处理方式。当不使用lazy时,filter处理整个数组并将结果存储到一个新数组中。当使用 lazy 时,序列或集合中的值是 按需 从下游函数生成的。这些值不存储在数组中;它们只在需要时生产。

考虑一下我使用 reduce 而不是 count 的修改示例,这样我们就可以打印出正在发生的事情:

未使用 lazy:

在这种情况下,将首先过滤所有项目,然后再计算任何内容。

[1, 2, 3, -1, -2].filter({ print("filtered one"); return [=10=] > 0 })
    .reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
filtered one
filtered one
filtered one
filtered one
counted one
counted one
counted one

使用lazy:

在这种情况下,reduce 要求计算一个项目,filter 会一直工作直到找到一个,然后 reduce 会要求另一个,filter 将一直工作,直到找到另一个。

[1, 2, 3, -1, -2].lazy.filter({ print("filtered one"); return [=12=] > 0 })
    .reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
counted one
filtered one
counted one
filtered one
counted one
filtered one
filtered one

什么时候使用lazy:

选项-点击lazy给出了这个解释:

来自关于lazy的讨论

链接操作时使用惰性属性:

  1. 防止中间操作分配存储

  2. 当你只需要最终集合的一部分以避免不必要的计算时

    我要添加第三个:

  3. 当您希望下游进程更快启动而不必等待上游进程先完成所有工作时

因此,例如,如果您要搜索第一个正数 Int,您会希望在 filter 之前使用 lazy,因为搜索会在您搜索后立即停止找到了一个,它可以节省 filter 不必过滤整个数组,也可以节省为过滤后的数组分配 space。

对于第三点,假设您有一个程序显示 1...10_000_000 范围内的质数,并在该范围内使用 filter。您宁愿在找到素数时显示它们,也不愿在显示任何内容之前等待计算所有素数。