结合懒惰的 Iterables
标准库附带了大量惰性迭代(以及 Iterators.jl 等库提供了更多)。可以组合延迟迭代以在恒定时间内创建更强大的迭代。最重要的延迟迭代是取放 ,从中可以创建许多其他函数。
懒惰切片一个可迭代的
可以使用切片表示法切割数组。例如,以下内容返回数组的第 10 个到第 15 个元素,包括:
A[10:15]
但是,切片表示法不适用于所有迭代。例如,我们不能切片生成器表达式:
julia> (i^2 for i in 1:10)[3:5]
ERROR: MethodError: no method matching getindex(::Base.Generator{UnitRange{Int64},##1#2}, ::UnitRange{Int64})
切片字符串可能没有预期的 Unicode 行为:
julia> "αααα"[2:3]
ERROR: UnicodeError: invalid character index
in getindex(::String, ::UnitRange{Int64}) at ./strings/string.jl:130
julia> "αααα"[3:4]
"α"
我们可以定义一个函数 lazysub(itr, range::UnitRange)
来对任意迭代进行这种切片。这是根据 take
和 drop
定义的:
lazysub(itr, r::UnitRange) = take(drop(itr, first(r) - 1), last(r) - first(r) + 1)
这里的实现有效,因为对于 UnitRange
值 a:b
,执行以下步骤:
- 放下第一个
a-1
元素 - 取
a
th 元素,a+1
th 元素,等等,直到a+(b-a)=b
th 元素
总共采用了 b-a
元素。我们可以在上面的每种情况下确认我们的实施是否正确
julia> collect(lazysub("αααα", 2:3))
2-element Array{Char,1}:
'α'
'α'
julia> collect(lazysub((i^2 for i in 1:10), 3:5))
3-element Array{Int64,1}:
9
16
25
懒惰地循环移动
对数组的 circshift
操作将使数组移位,就像它是一个圆,然后重新线性化它。例如,
julia> circshift(1:10, 3)
10-element Array{Int64,1}:
8
9
10
1
2
3
4
5
6
7
我们可以懒洋洋地为所有迭代事件做这件事吗?我们可以使用 cycle
,drop
和 take
iterables 来实现此功能。
lazycircshift(itr, n) = take(drop(cycle(itr), length(itr) - n), length(itr))
随着懒惰类型在许多情况下的性能更高,这使我们可以在不支持它的类型上执行类似 circshift
的功能:
julia> circshift("Hello, World!", 3)
ERROR: MethodError: no method matching circshift(::String, ::Int64)
Closest candidates are:
circshift(::AbstractArray{T,N}, ::Real) at abstractarraymath.jl:162
circshift(::AbstractArray{T,N}, ::Any) at abstractarraymath.jl:195
julia> String(collect(lazycircshift("Hello, World!", 3)))
"ld!Hello, Wor"
Version >= 0.5.0
制作乘法表
让我们使用惰性可迭代函数创建一个乘法表来创建一个矩阵。
这里使用的关键功能是:
解决方案是:
julia> map(prod, Base.product(1:10, 1:10))
10×10 Array{Int64,2}:
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100