multidimensional-arrayinitializationjuliacomposite-typestype-constructor

Julia: Making empty/initialized multidimensional arrays of self defined types


I am making a type of my own called KeyVal defined as below:

type KeyVal
    first::Int
    second::Float64
end

And I am trying to make an empty/or initialized at zero matrix that its elements are of type KeyVal. Normally with other types I do

myMat = zeros(KeyVal, (10,3))

But this will not work, because there is no zeros defined for this composite type. So I try to define my own zeros function in the following way:

import Base.zeros

function zeros(KeyVal,dims)
    if length(dims) > 1
        n=dims[1]
        m=dims[2]
        temp = repeat([KeyVal(0,0.0)], outer=m*n)
        temp = reshape(temp, (n,m))
        return temp
    elseif length(dims) == 1
        n=dims[1]
        temp= repeat([KeyVal(0,0.0)], outer=n)
        temp = reshape(temp, (n))
        return temp
    end
end

This adds to the methods list of other previously defined zeros. But using it will generate errors:

myMat = zeros(KeyVal, (N,M))

MethodError: no method matching zero(::Type{KeyVal})
Closest candidates are:
    .....

I am wondering whether I can resolve this in a way or maybe signal this in the type constructor so that any data structure that involves the type KeyVal is initialized at (first = 0, second=0.0).

Previously I tried defining the matrix as :

myMat2 = Array{KeyVal,(N,M)}

This will create the matrix except all its elements are #undef, and in this case I cannot access any of the elements of the myMat2:

myMat2[1,1]
UndefRefError: access to undefined reference

Solution

  • You can create an uninitialized array of any type T and dimension N with

    Array{T, N}(undef, dims...)
    

    In the special case of one and two dimensional arrays, you can use the aliases Vector and Matrix, e.g.,

    julia> m = Matrix{KeyVal}(undef, 2,2)
    2×2 Array{KeyVal,2}:
     #undef  #undef
     #undef  #undef
    

    You can then set the elements as usual, e.g.,

    m[1,2] = KeyVal(1,2)
    

    If it makes sense for your type to implement a zero method, you can define

    Base.zero(::Type{KeyVal}) = KeyVal(0,0)
    

    and zeros will work correctly

    julia> zeros(KeyVal, (2,2))
    2×2 Array{KeyVal,2}:
     KeyVal(0,0.0)  KeyVal(0,0.0)
     KeyVal(0,0.0)  KeyVal(0,0.0)
    

    Warning:

    zeros uses fill! which fills the array with copies of the same instance. Instead, use the comprehension below or an immutable type that doesn't contain references (isbits is true). The latter approach is likely faster as well because the array can be stored as one continuous block of memory. In that case, you can also use fill(KeyVal(0,0), dims..) directly instead of defining zero. On 0.6, the new struct keyword creates an immutable type by default.

    Edit:

    Another possibility is to use a comprehension

    julia> m = [KeyVal() for i=1:3, j=1:2]
    3×2 Array{KeyVal,2}:
     KeyVal(0,0.0)  KeyVal(0,0.0)
     KeyVal(0,0.0)  KeyVal(0,0.0)
     KeyVal(0,0.0)  KeyVal(0,0.0)
    

    For convenience, I previously defined the outer constructor

    KeyVal() = KeyVal(0,0)