Julia splat 运算符拆包
Julia splat operator unpacking
在 Python 中,可以使用 *
运算符来解包可迭代对象。
In [1]: head, *tail = [1, 2, 3, 4, 5]
In [2]: head
Out[2]: 1
In [3]: tail
Out[3]: [2, 3, 4, 5]
我想在 Julia 中产生相同的行为。我认为等效的 ...
运算符可以工作,但它似乎只是在这种情况下产生错误。
julia> head, tail... = [1, 2, 3, 4, 5]
ERROR: syntax: invalid assignment location "tail..."
我能够使用以下方法生成我想要的结果,但这是一个丑陋的解决方案。
julia> head, tail = A[1], A[2:end]
(1,[2,3,4,5])
我可以使用 splat (...
) 运算符解压数组,使 tail
包含 head
之后的其余项目吗?如果不是,最干净的替代品是什么?
编辑: 此功能已在 #2626 中提出。看起来它将成为 1.0 版本的一部分。
这确实听起来像是宏的工作:
function unpack(lhs, rhs)
len = length(lhs.args)
if len == 1
# just remove the splatting
l, is_splat = remove_splat(lhs.args[1])
return :($l = $(esc(rhs)))
else
new_lhs = :()
new_rhs = quote
tmp = $(esc(rhs))
$(Expr(:tuple))
end
splatted = false
for (i, e) in enumerate(lhs.args)
l, is_splat = remove_splat(e)
if is_splat
splatted && error("Only one splatting operation allowed on lhs")
splatted = true
r = :(tmp[$i:end-$(len-i)])
elseif splatted
r = :(tmp[end-$(len-i)])
else
r = :(tmp[$i])
end
push!(new_lhs.args, l)
push!(new_rhs.args[4].args, r)
end
return :($new_lhs = $new_rhs)
end
end
remove_splat(e::Symbol) = esc(e), false
function remove_splat(e::Expr)
if e.head == :(...)
return esc(e.args[1]), true
else
return esc(e), false
end
end
macro unpack(expr)
if Meta.isexpr(expr, :(=))
if Meta.isexpr(expr.args[1], :tuple)
return unpack(expr.args[1], expr.args[2])
else
return unpack(:(($(expr.args[1]),)), expr.args[2])
end
else
error("Cannot parse expression")
end
end
它没有经过很好的测试,但基本的东西是有效的:
julia> @unpack head, tail... = [1,2,3,4]
(1,[2,3,4])
julia> @unpack head, middle..., tail = [1,2,3,4,5]
(1,[2,3,4],5)
一些 Julia 陷阱:
x,y = [1,2,3] #=> x = 1, y = 2
a = rand(3)
a[1:3], y = [1,2,3] #=> a = [1.0,1.0,1.0], y = 2
宏遵循此行为
@unpack a[1:3], y... = [1,2,3]
#=> a=[1.0,1.0,1.0], y=[2,3]
从 Julia 1.6 开始
It is now possible to use ...
on the left-hand side of destructured assignments for taking any number of items from the front of an iterable collection, while also collecting the rest.
分配前两项并吞掉其余项的示例:
julia> a, b, c... = [4, 8, 15, 16, 23, 42]
# 6-element Vector{Int64}:
# 4
# 8
# 15
# 16
# 23
# 42
julia> a
# 4
julia> b
# 8
julia> c
# 4-element Vector{Int64}:
# 15
# 16
# 23
# 42
This syntax is implemented using Base.rest
, which can be overloaded to customize its behavior.
重载 Base.rest(s::Union{String, SubString{String}}, i::Int)
以吸取 Vector{Char}
而不是默认值 SubString
的示例:
julia> a, b... = "hello"
julia> b
# "ello"
julia> Base.rest(s::Union{String, SubString{String}}, i=1) = collect(SubString(s, i))
julia> a, b... = "hello"
julia> b
# 4-element Vector{Char}:
# 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
# 'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
# 'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
# 'o': ASCII/Unicode U+006F (category Ll: Letter, lowercase)
在 Python 中,可以使用 *
运算符来解包可迭代对象。
In [1]: head, *tail = [1, 2, 3, 4, 5]
In [2]: head
Out[2]: 1
In [3]: tail
Out[3]: [2, 3, 4, 5]
我想在 Julia 中产生相同的行为。我认为等效的 ...
运算符可以工作,但它似乎只是在这种情况下产生错误。
julia> head, tail... = [1, 2, 3, 4, 5]
ERROR: syntax: invalid assignment location "tail..."
我能够使用以下方法生成我想要的结果,但这是一个丑陋的解决方案。
julia> head, tail = A[1], A[2:end]
(1,[2,3,4,5])
我可以使用 splat (...
) 运算符解压数组,使 tail
包含 head
之后的其余项目吗?如果不是,最干净的替代品是什么?
编辑: 此功能已在 #2626 中提出。看起来它将成为 1.0 版本的一部分。
这确实听起来像是宏的工作:
function unpack(lhs, rhs)
len = length(lhs.args)
if len == 1
# just remove the splatting
l, is_splat = remove_splat(lhs.args[1])
return :($l = $(esc(rhs)))
else
new_lhs = :()
new_rhs = quote
tmp = $(esc(rhs))
$(Expr(:tuple))
end
splatted = false
for (i, e) in enumerate(lhs.args)
l, is_splat = remove_splat(e)
if is_splat
splatted && error("Only one splatting operation allowed on lhs")
splatted = true
r = :(tmp[$i:end-$(len-i)])
elseif splatted
r = :(tmp[end-$(len-i)])
else
r = :(tmp[$i])
end
push!(new_lhs.args, l)
push!(new_rhs.args[4].args, r)
end
return :($new_lhs = $new_rhs)
end
end
remove_splat(e::Symbol) = esc(e), false
function remove_splat(e::Expr)
if e.head == :(...)
return esc(e.args[1]), true
else
return esc(e), false
end
end
macro unpack(expr)
if Meta.isexpr(expr, :(=))
if Meta.isexpr(expr.args[1], :tuple)
return unpack(expr.args[1], expr.args[2])
else
return unpack(:(($(expr.args[1]),)), expr.args[2])
end
else
error("Cannot parse expression")
end
end
它没有经过很好的测试,但基本的东西是有效的:
julia> @unpack head, tail... = [1,2,3,4]
(1,[2,3,4])
julia> @unpack head, middle..., tail = [1,2,3,4,5]
(1,[2,3,4],5)
一些 Julia 陷阱:
x,y = [1,2,3] #=> x = 1, y = 2
a = rand(3)
a[1:3], y = [1,2,3] #=> a = [1.0,1.0,1.0], y = 2
宏遵循此行为
@unpack a[1:3], y... = [1,2,3]
#=> a=[1.0,1.0,1.0], y=[2,3]
从 Julia 1.6 开始
It is now possible to use
...
on the left-hand side of destructured assignments for taking any number of items from the front of an iterable collection, while also collecting the rest.
分配前两项并吞掉其余项的示例:
julia> a, b, c... = [4, 8, 15, 16, 23, 42]
# 6-element Vector{Int64}:
# 4
# 8
# 15
# 16
# 23
# 42
julia> a
# 4
julia> b
# 8
julia> c
# 4-element Vector{Int64}:
# 15
# 16
# 23
# 42
This syntax is implemented using
Base.rest
, which can be overloaded to customize its behavior.
重载 Base.rest(s::Union{String, SubString{String}}, i::Int)
以吸取 Vector{Char}
而不是默认值 SubString
的示例:
julia> a, b... = "hello"
julia> b
# "ello"
julia> Base.rest(s::Union{String, SubString{String}}, i=1) = collect(SubString(s, i))
julia> a, b... = "hello"
julia> b
# 4-element Vector{Char}:
# 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
# 'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
# 'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
# 'o': ASCII/Unicode U+006F (category Ll: Letter, lowercase)