a06e
a06e

Reputation: 20774

Add a row to a matrix inside a function (and propagate the changes outside) in Julia?

This is similar to this question:

Add a row to a matrix in Julia?

But now I want to grow the matrix inside a function:

function f(mat)
    mat = vcat(mat, [1 2 3])
end

Now, outside this function:

mat = [2 3 4]
f(mat)

But this doesn't work. The changes made to mat inside f aren't propagated outside, because a new mat was created inside f (see http://docs.julialang.org/en/release-0.4/manual/faq/#functions).

Is it possible to do what I want?

Upvotes: 2

Views: 266

Answers (2)

Tasos Papastylianou
Tasos Papastylianou

Reputation: 22215

I agree with Fengyang's very comprehensive answer and excellent avoidance of an XY Problem. Just adding my 2¢.

push!ing is a fairly expensive operation, since at each push a new location in memory needs to be found to accommodate the new variable of larger size.

If you care about efficiency, then it would be a lot more prudent to preallocate your mat and mutate it (i.e. alter its contents) inside your function, which is permitted, i.e.

julia> mat = Array(Int64, (100,3)); # mat[1,:] contains garbage values
julia> f(mat, ind, x) = mat[ind,:] = x;
julia> f(mat, 1, [1 2 3]);          # mat[1,:] now contains [1,2,3]

If the reason you prefer the push! approach is because you don't want to keep track of the index and pass it manually, then you can automate this process in your function too, by keeping a mutatable counter e.g.

function f(mat, c, x)
  c[1] = c[1] + 1;
  mat[c[1], :] = x;
end;

julia> mat = Array(Int64, (100,3)); counter = [0];
julia> f(mat, counter, [1 2 3]);
julia> f(mat, counter, [1 2 3]);
julia> f(mat, counter, [1 2 3]);
julia> mat[1:3,:]
  3×3 Array{Int64,2}:
   1  2  3
   1  2  3
   1  2  3

Alternatively, you can even create a closure, i.e. a function with state, that has an internal counter, and forget about keeping an external counter variable, e.g.

julia> f = () -> (); # creating an f at 'outer' scope
julia> let c = 1     # creates c at local 'let' scope
         f = (mat, x) -> (mat[c,:] = x; c += 1;) # f reassigned from outer scope
       end;          # f now contains the 'closed' variable c 
julia> mat = Array(Int64, (100,3));
julia> f(mat, [1 2 3]);
julia> f(mat, [2 3 4]);
julia> f(mat, [3 4 5]);
julia> mat[1:3,:]
  3×3 Array{Int64,2}:
   1  2  3
   2  3  4
   3  4  5

Upvotes: 0

Fengyang Wang
Fengyang Wang

Reputation: 12051

Multi-dimensional arrays cannot have their size changed. There are pointer hacks to share data, but these do not modify the size of the original array.


Even if it were possible, be aware that because Julia matrices are column major, this operation is very slow, and requires a copy of the array.

In Julia, operations that modify the data passed in (i.e., performing computations on data instead of with data) are typically marked with !. This denotes to the programmer that the collection being processed will be modified. These kinds of operations are typically called "in-place" operations, because although they are harder to use and reason about, they avoid using additional memory, and can usually complete faster.

There is no way to avoid a copy for this operation because of how matrices are stored in memory. So there is not much real benefit to turning this particular operation into an in-place operation. Therefore, I recommend against it.


If you really need this operation for some reason, you should not use a matrix, but rather a vector of vectors:

v = Vector{Float64}[]
push!(v, [1.0, 2.0, 3.0])

This data structure is slightly slower to access, but much faster to add to.

On the other hand, from what it sounds like, you may be interested in a more specialized data structure, such as a DataFrame.

Upvotes: 7

Related Questions