在 Julia 中压缩到最长长度(就像 Python 的 zip_longest)

问题描述 投票:0回答:3

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
吗?

我刚刚一直在使用自定义函数来手动填充+压缩,但我想知道我是否错过了更多内置的东西。

iterator julia zip
3个回答
1
投票

这有点冗长,但你可以这样做:

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
相同的方式显示)。


1
投票

如果你对

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) 

0
投票

为了完整起见,我的方法是

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)
    
© www.soinside.com 2019 - 2024. All rights reserved.