How to declare a 2d array whose rows vary in length in Julia?

Viewed 157

I want to declare a 2d array which takes the form of zigzag shape as follows:

***
************
******
*
*
**

where each star denotes an integer. This means that the lengths of its rows differ top to bottom. A vector of a vector seems to fit in this case and my code is:


struct In
  cols :: Vector{Int};
end 

struct Out
  nlen :: Int;
  rows :: Vector{In};
  function Out(n)  
    o = new(n, Vector{In}(undef,n));
    
    for i in 1:n
      o.rows(i).cols = Vector{Int}(undef,i);
    end
    

    return o

  end
end


myarray = Out(5);

Running the above code results in the error:

LoadError("main.jl", 24, MethodError(In[#undef, #undef, #undef, #undef, #undef], (1,), 0x0000000000006a05))

I wonder what is wrong with the implementation and hope to know other ways to meet the need here.

4 Answers

Your code tries to allocate value to a null reference. This happens because the elements of rows are initialized and you cannot access their cols field.

Insetad of

o.rows(i).cols = Vector{Int}(undef,i);

You should type:

o.rows[i] = In(Vector{Int}(undef,i));

Actually the entire code in the constructor can be shortened to:

Out(n) = new(n, [In(Vector{Int}(undef,i)) for i in 1:n])

Maybe it's because your example is a stripped down version of something more complicated, but making your own structs to hold Vectors seems overkill. I'd just use array comprehension and some way to initialize with dummy values, like zero and zeros:

function make_jagged(::Type{T}, col_lengths...) where T
    [zeros(T, i) for i in col_lengths]
end

make_jagged(Int64, 2,5,3,6)
4-element Array{Array{Int64,1},1}:
 [0, 0]
 [0, 0, 0, 0, 0]
 [0, 0, 0]
 [0, 0, 0, 0, 0, 0]

If you want to stick with your original code, read on.

The error you ran into happens because they restrict how you can access uninitialized non-isbitstypes. In is a non-isbitstype because it stores a mutable cols with a pointer to somewhere else in memory. A pointer to uninitialized memory is a dangling pointer, so for your own (memory) safety, you're not allowed to get it o.rows[i] or access its fields o.rows[i].cols.

All you're allowed to do with an uninitialized non-isbitstype element is set a proper instance to it, like:

# o.rows is a Vector{In}(undef,n)
    for i in 1:n
      o.rows[i] = In( Vector{Int}(undef,i) );
    end

Thank you both. The following code would be good for the task:

struct JaggedMat{TT}
  nlen     :: Int;
  nlenvec  :: Vector{Int};
  rows     :: Vector{Vector{TT}};
end



function JaggedMat{TT}(n,nlenvec) where TT
  o = JaggedMat(n, nlenvec,Vector{Vector{TT}}(undef,n));
  
  
  for i in 1:n
    o.rows[i] = Vector{TT}(undef,max(nlenvec[i],1) );

    # initialization
    for j in 1:nlenvec[i]
      o.rows[i][j] = 0 ;
    end
    
  end
  
  return o

end



jagged_mat = JaggedMat{Float64}(3,Int[1,2,3])


If you don't mind using an external package:


using PackedVectorsOfVectors

jagged_mat = PackedVectorsOfVectors.allocate_packed(Float64,undef,[1,2,3]

#filling with zeros:
#option one, by using package internals
jagged_mat.p .= 0.0

#option two, iterating:
for vector in jagged_mat
  vector .= 0.0
end

The documentation of the package is here https://synchronoustechnologies.github.io/PackedVectorsOfVectors.jl/stable/

Related