Automatic building of a vector containing modified instances of a structure

Dear all

I have the following structure MyPar (this is a reproductible fictive example):

Base.@kwdef struct MyPar
    n1::Int = 1
    ep::Float64 = .01
    co::Float64 = 1.
    dg::Int = 3
    it::Bool = false
end
julia> par = MyPar()   # Default instance
MyPar(1, 0.01, 1.0, 3, false)
## Example of instance where some values are modified from the default 
julia> par = MyPar(ep = 1.23, co = 12)
MyPar(1, 1.23, 12.0, 3, false)

And now I have the following NamedTuple, say pars, containing vectors (of equal lengths) of values for some of the elements of MyPar (the elements in pars can vary, below is just an example, other elements than ep and dg can be present):

julia> pars = (ep = [10, -7., .8], dg = [1, 25, 4])
(ep = [10.0, -7.0, 0.8], dg = [1, 25, 4])

What I would like is to automatically create a vector, say listpar, of length = length(pars[1]) whose each element “i” will contains an instance of MyPar where the values are modified from the contents of pars, i.e. in the above example of pars:

## Building listpar by hand 
## (this what I would like to automatize) 
ncomb = length(pars[1])
listpar = Vector(undef, n)
i = 1 ; listpar[i] = MyPar(ep = pars.ep[i], dg = pars.dg[i])
i = 2 ; listpar[i] = MyPar(ep = pars.ep[i], dg = pars.dg[i])
i = 3 ; listpar[i] = MyPar(ep = pars.ep[i], dg = pars.dg[i])
## Expected result 
julia> listpar
3-element Vector{Any}:
 MyPar(1, 10.0, 1.0, 1, false)
 MyPar(1, -7.0, 1.0, 25, false)
 MyPar(1, 0.8, 1.0, 4, false)

Do you see a way to do this automatically, given MyPar and an object pars?

I found a quasi-solution (below, probably not optimal) but I needed to use a mutable structure (MyParMut, below) and, what I did not want, to create a global variable (par_tmp, below):

Base.@kwdef mutable struct MyParMut
    n1::Int = 1
    ep::Float64 = .01
    co::Float64 = 1.
    dg::Int = 3
    it::Bool = false
end
pars = (ep = [10, -7., .8], dg = [1, 25, 4])   ## A given pars
nampars = keys(pars)
npars = length(nampars) 
ncomb = length(pars[1])
listpar = Vector(undef, ncomb)
for i = 1:ncomb
    global par_tmp = MyParMut()
    for j = 1:npars
        nam = nampars[j]
        val = pars[nam][i]
        z = string("par_tmp.", nam, "=", val)
        eval(Meta.parse(z))    ## Problem here since eval works in the global scope
    end
    listpar[i] = par_tmp 
end
julia> listpar
3-element Vector{Any}:
 MyPar(1, 10.0, 1.0, 1, false)
 MyPar(1, -7.0, 1.0, 25, false)
 MyPar(1, 0.8, 1.0, 4, false)

I hope my description was clear. Any idea to get an automatic way to do this (idealy from MyPar, but from MyParMut without the creation of a global variable would also be ok) would be appreciate.

julia> function my_pars(pars)
           v = Vector{MyPar}(undef, 0)
           for i in eachindex(pars[1])
               push!(v, MyPar(ep = pars.ep[i], dg = pars.dg[i]))
           end
           return v
       end
my_pars (generic function with 1 method)

julia> pars = (ep = [10, -7., .8], dg = [1, 25, 4])
(ep = [10.0, -7.0, 0.8], dg = [1, 25, 4])

julia> my_pars(pars)
3-element Vector{MyPar}:
 MyPar(1, 10.0, 1.0, 1, false)
 MyPar(1, -7.0, 1.0, 25, false)
 MyPar(1, 0.8, 1.0, 4, false)

Thanks but your fonction only works for ep and dg (this is not what I look for):

I think I did not emphasize enough the fact that pars can vary (I edited my first message), i.e. not always contain the ep or dg elements (but any of the ones belonging to MyPar), for instance it can be:

julia> pars = (ep = [10, -7.], co = [-1., .28])
(ep = [10.0, -7.0], co = [-1.0, 0.28])

or

julia> pars = (n1 = collect(1:5),)
(n1 = [1, 2, 3, 4, 5],)

or

julia> pars = (ep = [10, -7.], it = [true, true], co = [-1., .28])
(ep = [10.0, -7.0], it = Bool[1, 1], co = [-1.0, 0.28])

etc.

This is the difficulty I try to solve

1 Like

With MyPar and pars as in the OP:

julia> [MyPar(;kws...) for 
    kws in zip([[k=>vv for vv in v] for (k,v) in pairs(pars)]...)]
3-element Vector{MyPar}:
 MyPar(1, 10.0, 1.0, 1, false)
 MyPar(1, -7.0, 1.0, 25, false)
 MyPar(1, 0.8, 1.0, 4, false)

This isn’t optimized in terms of allocations. But might be enough.

2 Likes

Clearly it is enough! Amazing! many thanks @Dan