Base.Iterators.zip
在最短的迭代器处终止,所以这里 Carol/David/100 被丢弃:
julia> a = [1, 2]
julia> b = ["Alice", "Bob", "Carol", "David"]
julia> c = [90, 85, 100]
julia> println.(zip(a, b, c))
# (1, "Alice", 90)
# (2, "Bob", 85)
有时我想
zip
到像Python的最长迭代器itertools.zip_longest
:
>>> print(list(zip_longest(a, b, c)))
# [(1, 'Alice', 90),
# (2, 'Bob', 85),
# (None, 'Carol', 100),
# (None, 'David', None)]
有朱利安版的
zip_longest
吗?
我刚刚一直在使用自定义函数来手动填充+压缩,但我想知道我是否错过了更多内置的东西。
这有点冗长,但你可以这样做:
julia> zip(
Iterators.flatten((a, Iterators.repeated(nothing))),
b,
Iterators.flatten((c, Iterators.repeated(nothing))),
) |> collect
4-element Vector{Tuple{Any, String, Any}}:
(1, "Alice", 90)
(2, "Bob", 85)
(nothing, "Carol", 100)
(nothing, "David", nothing)
但是,这需要知道
b
是最长的迭代器。为了处理任何最短的迭代器,您需要做一些更复杂的事情。这将做到:
function zip_longest(itrs...)
itrs′ = map(itrs) do itr
itr = Iterators.map(Some, itr)
Iterators.flatten((itr, Iterators.repeated(nothing)))
end
itr = Iterators.takewhile(zip(itrs′...)) do t
!all(isnothing, t)
end
Iterators.map(itr) do t
map(x -> isnothing(x) ? x : something(x), t)
end
end
在事先不知道哪个迭代器最长的情况下,这会做你想做的事:
julia> zip_longest(a, b, c) |> collect
4-element Vector{Tuple{Any, String, Any}}:
(1, "Alice", 90)
(2, "Bob", 85)
(nothing, "Carol", 100)
(nothing, "David", nothing)
但是,如果我正在为 IterTools 贡献一个实现,我会定义一个
ZipLongest
类型并写出显式迭代逻辑(并提供显示方法以与 zip
相同的方式显示)。
如果你对
Vector{Tuple}
类似于 Python 的输出感到满意,你可以使用这样的理解:
zip_longest(xs...) = [get.(xs,i,nothing) for i in 1:maximum(length.(xs))]
思路是使用
get
的默认输出来填充缺失值
a = [1, 2]
b = ["Alice", "Bob", "Carol", "David"]
c = [90, 85, 100]
zip_longest(a,b,c)
4-element Vector{Tuple{Any, String, Any}}:
(1, "Alice", 90)
(2, "Bob", 85)
(nothing, "Carol", 100)
(nothing, "David", nothing)
fill
迭代器的最长长度并解压成 zip
:
function zip_longest(iters...)
filled = [[i; fill(nothing, maximum(length, iters) - length(i))] for i in iters]
return zip(filled...)
end
我对 Julia 的理解还不够流利,无法理解所有的(缺点)优势,但从目前的答案中可以得出一些结论:
每个版本返回不同的类型:
typeof(zip_longest_stefan(a, b, c)) # Base.Generator{...}
typeof(zip_longest_abo(a, b, c)) # Vector{...}
typeof(zip_longest_tdy(a, b, c)) # Base.Iterators.Zip{...}
我的版本返回更混乱的收集类型:
typeof(zip_longest_stefan(a, b, c) |> collect) # Vector{Tuple{Any, String, Any}}
typeof(zip_longest_abo(a, b, c) |> collect) # Vector{Tuple{Any, String, Any}}
typeof(zip_longest_tdy(a, b, c) |> collect) # Vector{Tuple{Union{Nothing, Int64}, Union{Nothing, String}, Union{Nothing, Int64}}}
Stefan 的生成器版本很快(假设我的基准测试正确):
using BenchmarkTools
a = rand(1:100, 1000)
b = rand(1:100, 1000000)
c = rand(1:100, 100)
d = rand(1:100, 10000)
@btime zip_longest_stefan(a, b, c, d) # 920.800 ns (12 allocations: 736 bytes)
@btime zip_longest_abo(a, b, c, d) # 160.438 ms (1000136 allocations: 68.82 MiB)
@btime zip_longest_tdy(a, b, c, d) # 3.416 ms (18 allocations: 34.33 MiB)