在不使用Collect()的情况下从Julia中的范围创建数组

spe*_*max 2 julia

从范围创建数组时,我对Julia的这种行为感到有点困惑。我想了解以下内容的基本机制。

A = [1:10]
Run Code Online (Sandbox Code Playgroud)

结果是 1-element Array{UnitRange{Int64},1}

这不是我想要的。上面的代码创建一个Array UnitRange。Julia文档建议使用collect()从范围创建数组,如下所示:

A = collect(1:10)
Run Code Online (Sandbox Code Playgroud)

结果10-element Array{Int64,1}。完善。

但是,如果我在范围后添加分号,则此代码也可以使用。

A = [1:10;]
Run Code Online (Sandbox Code Playgroud)

根据Julia的文档,;是进行vcat()垂直连接或串联的简写形式。什么是意义vcat使用它作为时A = [1:10;]。它不仅看起来很奇怪(用什么来吸引它?),对我来说也没有意义。

我希望就范围如何与vcat交互进行清晰的解释。

Gni*_*muc 5

range是永不分配的“惰性”向量。它可能是最有用的迭代器之一。

julia> AbstractRange <: AbstractVector
true

julia> @allocated [1,2,3,4,5,6,7,8,9,10]
160

julia> @allocated 1:10
0
Run Code Online (Sandbox Code Playgroud)

范围运算符:用于创建范围:

julia> 1:10 |> dump
UnitRange{Int64}
  start: Int64 1
  stop: Int64 10
Run Code Online (Sandbox Code Playgroud)

您已经知道了如何使用来将范围转换为向量collect,但是如果您可以更深入地研究代码,则会发现collect实际上是vcat在后台进行调用:

julia> @less collect(1:10)

collect(r::AbstractRange) = vcat(r) 
Run Code Online (Sandbox Code Playgroud)

这是如何vcat处理AbstractRange输入的:

@less vcat(1:10)    

function vcat(rs::AbstractRange{T}...) where T
    n::Int = 0
    for ra in rs
        n += length(ra)
    end
    a = Vector{T}(undef, n)
    i = 1
    for ra in rs, x in ra
        @inbounds a[i] = x
        i += 1
    end
    return a
end
Run Code Online (Sandbox Code Playgroud)

实现非常简单,只需遍历输入(注意rs是vararg输入),然后将输入范围一对一地压缩到单个向量中即可。显然,即使在只有一个输入范围的情况下它也可以工作,例如的情况[1:10;]

还有一种从范围创建向量的方法:直接调用Vector构造函数Vector(1:10)。但是引擎盖下会发生什么?简单地调用@less Vector(1:10)不会直接跳到原始实现,这就是花哨的调试器出现的地方:

julia> using Debugger

julia> @enter Vector(1:10)
In Type(x) at boot.jl:424
>424  (::Type{Array{T,N} where T})(x::AbstractArray{S,N}) where {S,N} = Array{S,N}(x)

About to run: (Core.apply_type)(Array, Int64, 1)
1|debug> s
In Type(x) at boot.jl:424
>424  (::Type{Array{T,N} where T})(x::AbstractArray{S,N}) where {S,N} = Array{S,N}(x)

About to run: (Array{Int64,1})(1:10)
1|debug> s
[ Info: tracking Base
In Type(r) at range.jl:943
>943  Array{T,1}(r::AbstractRange{T}) where {T} = vcat(r)

About to run: (vcat)(1:10)
1|debug> s
In vcat(rs) at range.jl:930
>930  n::Int = 0
 931  for ra in rs
 932      n += length(ra)
 933  end
 934  a = Vector{T}(undef, n)

About to run: Core.NewvarNode(:(_5))
Run Code Online (Sandbox Code Playgroud)

如您所见,Vector还调用vcat

我认为这个示例已经为您提供了一些有关如何使用这些便捷的内置反射工具在Julia REPL中以交互方式找到答案的想法。还有像其他有用的工具@code_lowered@code_typed@macroexpand等,可以帮助你找出像“这是什么表情呢?”的问题,例如,

julia> f() = [1:10;]
f (generic function with 1 method)

julia> @code_lowered f()
CodeInfo(
1 ? %1 = 1:10
?   %2 = (Base.vcat)(%1)
???      return %2
)
Run Code Online (Sandbox Code Playgroud)

“降低的”代码告诉我们Julia首先创建一个范围%1 = 1:10,然后调用Base.vcat(%1),这正是文档所说的。

X-ref:Julia中的@ code_native,@ code_typed和@code_llvm有什么区别?