Search code examples
dataframejuliaanonymous-function

I would like to concatenate anonymous calls in Julia


So, I'm learning more about Julia and I would like to do the following:

I have a 3 row by 2 columns matrix, which is fixed, A = rand(2,3)

julia> A = rand(2,3)
2×3 Matrix{Float64}:
 0.705942  0.553562  0.731246
 0.205833  0.106978  0.131893

Then, I would like to have a anonymous function, which does the following:

a = ones(1,3);
a[2] = rand();

Finally, I would like to broadcast broadcast(+, ones(1,3) => a[2]=rand(), A)

So I have the middle column of A, i.e., A[:,2], added by two different random numbers, and in the rest of the columns, we add ones.

EDIT: If I add a, as it is:

julia> a = ones(1,3)
1×3 Matrix{Float64}:
 1.0  1.0  1.0

julia> a[2] = rand()
0.664824196431979

julia> a
1×3 Matrix{Float64}:
 1.0  0.664824  1.0

I would like that this a were dynamic, and a function.

So that:

broadcast(+, a, A)

Would give:

julia> broadcast(+, a, A)
2×3 Matrix{Float64}:
 1.70594  0.553562 + rand()  (correct)          1.73125
 1.20583  0.106970 + rand()  (different rand()) 1.13189

Instead of:

julia> broadcast(+, a, A)
2×3 Matrix{Float64}:
 1.70594  1.21839  (0.553562 + -> 0.664824)   1.73125
 1.20583  0.771802 (0.106978 + -> 0.664824)   1.13189 

So, I thought of this pseudo-code:

broadcast(+, a=ones(1,3) => a[2]=rand(), A)

Formalizing:

broadcast(+, <anonymous-fucntion>, A)

Second EDIT:

Rules/Constrains:

  • Rule 1: the call must be data-transparent. That is, A must not change state, just like when we call f.(A).
  • Rule 2: not creating an auxiliary variable (a must not exist). The only vector that must exist, before and after, the call is A.
  • Rule 3: f.(A) must be anonymous; that is, you can't use define f as function f(A) ... end

Solution

  • With the caveat that I don't know how much you really learn by setting artificial rules like this, some tidier ways are:

    julia> A = [ 0.705942  0.553562  0.731246
                 0.205833  0.106978  0.131893 ];  # as given
    
    julia> r = 0.664824196431979;  # the one random number
    
    julia> (A' .+ (1, r, 1))'  # no extra vector
    2×3 adjoint(::Matrix{Float64}) with eltype Float64:
     1.70594  1.21839   1.73125
     1.20583  0.771802  1.13189
    
    julia> mapslices(row -> row .+ (1, r, 1), A; dims=2)  # one line, but slow
    2×3 Matrix{Float64}:
     1.70594  1.21839   1.73125
     1.20583  0.771802  1.13189
    
    julia> B = A .+ 1; @views B[:, 2] .+= (-1 + r); B  # fast, no extra allocations
    2×3 Matrix{Float64}:
     1.70594  1.21839   1.73125
     1.20583  0.771802  1.13189
    

    I can't tell from your question whether you want one random number or two different ones. If you want two, then you can do this:

    julia> using Random
    
    julia> Random.seed!(1); mapslices(row -> row .+ (1, rand(), 1), A; dims=2)
    2×3 Matrix{Float64}:
     1.70594  0.675436  1.73125
     1.20583  0.771383  1.13189
    
    julia> Random.seed!(1); B = A .+ 1; @views B[:, 2] .+= (-1 .+ rand.()); B 
    2×3 Matrix{Float64}:
     1.70594  0.675436  1.73125
     1.20583  0.771383  1.13189
    

    Note that (-1 .+ rand.()) isn't making a new array on the right, it's fused by .+= into one loop over a column of B. Note also that B[:,2] .= stuff just writes into B, but B[:, 2] .+= stuff means B[:, 2] .= B[:, 2] .+ stuff and so, without @views, the slice B[:, 2] on the right would allocate a copy.