Проблема в передаче значения элементу структуры в Юлии - PullRequest
0 голосов
/ 10 января 2019

Скажите, что у меня есть структура:

mutable struct DataHolder
    data1::Vector{Float64}
    data2::Vector{Float64}
    function DataHolder()
        emp = Float64[]
        new(emp, emp)
    end
end
d = DataHolder()

Когда я пытаюсь передать значение только одному элементу структуры d, выполните:

push!(d.data1, 1.0)

значение выдвигается не только d.data1, но и d.data2. Действительно, REPL говорит

julia> d
DataHolder([1.0], [1.0])

Как я могу вставить значение только в один элемент структуры ??

Ответы [ 2 ]

0 голосов
/ 12 января 2019

@ ColinTBowers ответил на ваш вопрос. Вот очень простая и более общая реализация:

struct DataHolder  # add mutable if you really need it
    data1::Vector{Float64}
    data2::Vector{Float64}
end
DataHolder() = DataHolder([], [])

Возможно, вы хотели бы разрешить другие типы, кроме Float64 (потому что почему бы и нет?!):

struct DataHolder{T}
    data1::Vector{T}
    data2::Vector{T}
end
DataHolder{T}() where {T} = DataHolder{T}([], [])
DataHolder() = DataHolder{Float64}()  # now `Float64` is the default type.

Теперь вы можете сделать это:

julia> DataHolder{Rational}()
DataHold{Rational}(Rational[], Rational[])

julia> DataHolder()
DataHold{Float64}(Float64[], Float64[])
0 голосов
/ 10 января 2019

Ваша проблема не в push!, а во внутреннем конструкторе DataHolder. В частности:

emp = Float64[]
new(emp, emp)

Этот кодовый шаблон означает, что оба поля нового DataHolder указывают на один и тот же массив (в памяти). Поэтому, если вы изменяете одно из них (скажем, через push!), вы также изменяете другое.

Вместо этого вы можете заменить эти две строки на:

new(Float64[], Float64[])

чтобы получить желаемое поведение.

В более общем смысле, хотя это не запрещено, использование вами внутреннего конструктора немного странно. Как правило, внутренние конструкторы должны иметь сигнатуру метода, точно соответствующую полям вашей структуры, а сам внутренний конструктор обычно используется только для предоставления набора универсальных тестов, которые должен пройти любой новый DataHolder.

Лично я бы переписал ваш код следующим образом:

mutable struct DataHolder
    data1::Vector{Float64}
    data2::Vector{Float64}
    function DataHolder(data1::Vector{Float64}, data2::Vector{Float64})
        #Any tests on data1 and data2 go here
        new(data1, data2)
    end
end
DataHolder() = DataHolder(Float64[], Float64[])

Если вам не нужно выполнять какие-либо универсальные тесты для DataHolder, то полностью удалите внутренний конструктор.

Последняя пища для размышления: действительно ли DataHolder должно быть изменчивым? Если вы хотите иметь возможность изменять массивы только в data1 и data2, то сам по себе DataHolder не должен быть изменяемым, поскольку эти массивы уже изменяемы. Вам нужно, чтобы DataHolder был изменчивым, только если вы планируете полностью перераспределить значения в этих полях, например, операция вида dh.data1 = [2.0].

ОБНОВЛЕНИЕ ПОСЛЕ КОММЕНТАРИИ: Лично я не вижу ничего плохого в DataHolder() = DataHolder(Float64[], ..., Float64[]). Это всего лишь одна строка кода, и вам никогда не придется думать об этом снова. В качестве альтернативы вы можете сделать:

DataHolder() = DataHolder([ Float64[] for n = 1:10 ]...)

, который просто разбивает вектор пустых векторов на аргументы конструктора.

...