Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating Arrays from Ranges in Julia without using Collect()

Tags:

julia

I am a bit puzzled by this behavior in Julia when creating arrays from ranges. I want to know the underlying mechanics of the following.

A = [1:10]

Results in 1-element Array{UnitRange{Int64},1}

which is not what I wanted. Above code creates an Array of UnitRange. Julia documentation recommends using collect() to create arrays from ranges as follows:

A = collect(1:10)

Results in 10-element Array{Int64,1}. Perfect.

However, this code also works if I add a semicolon after the range.

A = [1:10;]

According to Julia documentation, ; is a short hand for vcat() or vertical concatenation. What is the significance of vcat when using it as A = [1:10;]. Not only does it look weird (what is it vcat'ing it with?), it doesn't make sense to me.

I'd love a clear explanation about how ranges interact with vcat.

like image 761
Neil Avatar asked Mar 31 '19 05:03

Neil


People also ask

How do you create an array of arrays in Julia?

An Array in Julia can be created with the use of a pre-defined keyword Array() or by simply writing array elements within square brackets([]). There are different ways of creating different types of arrays.

How do you create an empty array in Julia?

Empty Arrays To initialize an empty array, it is perfectly valid to use n = 0 in the above expressions.

How do you create a matrix in Julia?

Julia provides a very simple notation to create matrices. A matrix can be created using the following notation: A = [1 2 3; 4 5 6]. Spaces separate entries in a row and semicolons separate rows. We can also get the size of a matrix using size(A).


1 Answers

ranges are "lazy" vectors that never allocate. It's probably one of the most useful iterators.

julia> AbstractRange <: AbstractVector
true

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

julia> @allocated 1:10
0

the range operator : is for creating ranges:

julia> 1:10 |> dump
UnitRange{Int64}
  start: Int64 1
  stop: Int64 10

you've already known how to convert ranges to vectors using collect, but if you could dive a little bit deeper into the code, you would find collect actually calls vcat under the hood:

julia> @less collect(1:10)

collect(r::AbstractRange) = vcat(r) 

and this is how vcat deal with AbstractRange input:

@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

the implementation is very simple, just looping through the input (note rs is a vararg input), and concating input ranges one-by-one into a single vector. obviously, it works even when there is only one input range, which is the case of [1:10;].

there is another way to create vectors from ranges: directly calling the Vector constructor Vector(1:10). but what happens under the hood? simply calling @less Vector(1:10) won't directly jump to the original implementation, and this is where the fancy debugger comes in:

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))

as you can see, Vector also calls vcat.

I think this example has already given you some ideas about how to interactively find the answer in Julia REPL by yourself with these great handy built-in reflection tools. there are other useful tools like @code_lowered, @code_typed, @macroexpand etc. that could help you to figure out questions like "what does this expression do?", for example,

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
)

the "lowered" code tells us that Julia firstly create a range %1 = 1:10 and then call Base.vcat(%1), which is exactly what the documentation said.

X-ref: What is the difference between @code_native, @code_typed and @code_llvm in Julia?

like image 92
Gnimuc Avatar answered Oct 25 '22 00:10

Gnimuc