Replacing values of specific entries in an Array In Julia

I am wondering about how to implement efficiently a code that replaces some entries of an array with specific values. In other words, I have a matrix and a vector with some indices of the matrix where a logical condition holds. I want to change these entries for some values that I have in a vector.

So far, I’ve doing it with a loop, but perhaps there is a better strategy that uses filtering or something like that.

A small example would be:

A = collect(1:8); println(A)
   B = [10,20]
   C = A.<=2
   k = 1
   for t=1:8
                if C[t] ==  1
                    A[t] = B[k]
                    k = k+1
            else
       end
    end

However, I need to do this in inside a quite intensive loop, with a bigger matrix. The indices I have to change are always the same, but the vector of values (the counterpart of B) changes in each iteration.

Thanks so much!

2 Likes

You might be looking for:

A[A.<=2] .= B
8 Likes

That’s exactly what I am looking for! Thanks, it cuts the computing time in about 2/3s.

If you see a difference in runtime, it’s probably because you are running in global scope. Also, there’s no need to create the C.

1 Like

If this is performance critical for your application, I would advise to benchmark the different solutions properly to get a precise idea of what is going on.

Here is your first version:

function foo1!(A, B)
    C = A.<=2
    k = 1
    for t=1:length(A)
        if C[t] == 1
            A[t] = B[k]
            k = k+1
        end
    end
end
julia> A = collect(1:8);
julia> B = [-1, -2];
julia> using BenchmarkTools

julia> @btime foo1!($A, $B)
  579.562 ns (3 allocations: 4.31 KiB)

Next, the version proposed by simeonschaub, notably shorter and more readable, but also less efficient:

foo2!(A, B) = A[A.<=2] .= B
julia> @btime foo2!($A, $B)
  721.992 ns (9 allocations: 4.53 KiB)

Now, starting with your first implementation, there are some things which can be improved:

  • there is no need to compute C ahead of time: the comparison between A[t] and 2 can be performed in line
  • if the input data are correctly provided, the for loop should only access valid elements of A and B, and bounds checking can be elided.

This leads to the following implementation:

function foo3!(A, B)
    k = 1
    @inbounds for t in eachindex(A)
        if A[t] <= 2
            A[t] = B[k]
            k += 1
        end
    end
end

which is notably faster:

julia> @btime foo3!($A, $B)
  9.397 ns (0 allocations: 0 bytes)

Are your arrays always so small in the real application? Do you know the size in advance? If so, then there are still other optimizations which can be performed. But you’d have to tell us more about the context in which you want to use this.

7 Likes

You need to be careful with @inbounds because you probably don’t always know in advance, whether B actually holds enough values. If that’s the case, this would be a source of pretty nasty and hard-to-debug bugs, as it would just read random bits of memory. I’m still guessing that most of your performance gain comes from not having to allocate A.<=2 and doing this check in the loop, so I would only use @inbounds if you can guarantee that B is large enough.

Thanks so much for your reply! I implemented your solution and it seems to work pretty well. Truth is this fragment is just a part of a bigger function.

Dᵢ = rand([0],10,1) 
Dₑ = rand([1],10,1) 
D₀ = rand([0,1],10,10)  
Dt = max.(rand([0,1],10,10) , D₀)

B = zeros(10,10)
for j=1:10, t=1:10
B[j,t]=(0.95)^(t-1)
end
Π = 100*rand(10,10)

function value(D,Dᵢ,Dₑ)
Σ = Π .+ (75).(Dᵢ)
Σ = B .
D .* Σ
Σ = sum(Σ)
end
function jia_brute_force_3(Dᵢ,Dₑ,DL,DU)
Ddiff = DU - DL
cdiff=count(i->(i==1),Ddiff)
println(cdiff)

VU=value(DU,Dᵢ,Dₑ)
VL=value(DL,Dᵢ,Dₑ)

VOpt=0
DOptIndex=0

@showprogress for i = 0:2^cdiff-1
    int_s = collect(string(i, base=2, pad=cdiff))
    int_s = parse.(Int8,int_s)
    X=zeros(Int8,10,10)
    k = 1
   @inbounds for t in eachindex(Ddiff)
        if Ddiff[t] == 1
            X[t] = int_s[k]
            k = k+1
        else
        end
    end
    Dtemp = DL .+ X
    Vtemp=value(Dtemp,Dᵢ,Dₑ)
        if Vtemp>VOpt
            VOpt = Vtemp
            DOptIndex = i
        else
        end
end
int_s = collect(string(DOptIndex, base=2, pad=cdiff))
int_s = parse.(Int64,int_s)
DOpt = copy(DL)
DOpt[Ddiff.==1] .= int_s
return cdiff, VL, VU, VOpt, DOptIndex, DOpt

end
jia_brute_force_3(Dᵢ,Dₑ,D₀,Dt)

in which, first, I compute two bounds which are matrices of 0s and 1s and then I need to compute all the possible alternatives between the two bounds and evalute their value to pick the max. It is a combinatorial discrete choice problem. If you have any other observation in this function please let me know!

It is really difficult to optimize a piece of code that is not runnable. Please make a minimal working example that we can run. In particular:

  • Di, De, Psi, VU, VL, DU don’t seem to be relevant here, so you might want to simplify them away. I’m not sure about DL
  • the values of J, T, cdiff, etc. should be provided somewhere
  • provide a mock version of value
4 Likes

The example above should work!

Unfortunately, no. There are still a lot of errors. Please test the code in a fresh Julia console to check that everything is defined.

Also, please quote the code correctly:

2 Likes

I hope it’s alright to bump this old-ish conversation. I wanted to do what the OP states, “Replacing values of specific entries in an Array”, and found that François’s code could be adapted readily. I ended up with the following. But maybe there’s a better way now in 2021? Thanks.

Purpose: replace entries that are “small enough” by 0.0

"""
`zeroing!`
    Modify in place
"""
function zeroing!(M; ϵ = eps(Float64))
    k = 1
    for t in eachindex(M)
        if abs.(M[t]) <= ϵ
            M[t] = 0.
            k += 1
        end
    end
    return(M)
end

"""
`zeroing`
    Modify a deep copy
"""
function zeroing(A; ϵ = eps(Float64))
    M = deepcopy(A)
    return zeroing!(M, ϵ = ϵ)
end

Example array for testing:

M = [0    1e-10  
     0.0  1e-100]

Replacing a deep copy:

julia> zeroing(M)
2×2 Matrix{Float64}:
 0.0  1.0e-10
 0.0  0.0

No changes made to the original array:

julia> M
2×2 Matrix{Float64}:
 0.0  1.0e-10
 0.0  1.0e-100

Replacing in-place:

julia> zeroing!(M, ϵ = 1e-6)
2×2 Matrix{Float64}:
 0.0  0.0
 0.0  0.0

The original array has changed:

julia> M
2×2 Matrix{Float64}:
 0.0  0.0
 0.0  0.0
1 Like