为什么以及何时对 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
的用法。我曾在序列上使用过 map
、reduce
和 filter
函数,但从未在 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
的讨论:
链接操作时使用惰性属性:
防止中间操作分配存储
或
当你只需要最终集合的一部分以避免不必要的计算时
我要添加第三个:
当您希望下游进程更快启动而不必等待上游进程先完成所有工作时
因此,例如,如果您要搜索第一个正数 Int
,您会希望在 filter
之前使用 lazy
,因为搜索会在您搜索后立即停止找到了一个,它可以节省 filter
不必过滤整个数组,也可以节省为过滤后的数组分配 space。
对于第三点,假设您有一个程序显示 1...10_000_000
范围内的质数,并在该范围内使用 filter
。您宁愿在找到素数时显示它们,也不愿在显示任何内容之前等待计算所有素数。
[1, 2, 3, -1, -2].filter({ [=11=] > 0 }).count // => 3
[1, 2, 3, -1, -2].lazy.filter({ [=11=] > 0 }).count // => 3
在第二个语句中添加lazy
有什么好处。根据我的理解,当使用 lazy
变量时,内存在使用时被初始化为该变量。在这种情况下它有什么意义?
尝试更详细地了解 LazySequence
的用法。我曾在序列上使用过 map
、reduce
和 filter
函数,但从未在 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
的讨论:
链接操作时使用惰性属性:
防止中间操作分配存储
或
当你只需要最终集合的一部分以避免不必要的计算时
我要添加第三个:
当您希望下游进程更快启动而不必等待上游进程先完成所有工作时
因此,例如,如果您要搜索第一个正数 Int
,您会希望在 filter
之前使用 lazy
,因为搜索会在您搜索后立即停止找到了一个,它可以节省 filter
不必过滤整个数组,也可以节省为过滤后的数组分配 space。
对于第三点,假设您有一个程序显示 1...10_000_000
范围内的质数,并在该范围内使用 filter
。您宁愿在找到素数时显示它们,也不愿在显示任何内容之前等待计算所有素数。