简体   繁体   中英

Mutating a Julia dictionary through aliases

Let's say I have a dictionary like so:

my_object = Dict{Symbol, Any}(
    :foo => Dict{Symbol, Any}(
        :foo_items => ["item_a", "item_b", "item_c"],
        :bar => Dict{Symbol, Any}(
            :bar_name => ["name_a", "name_b", "name_c"],
            :type_before => ["Float32", "Float64", "String"],
            :type_after => ["Int32", "Int64", "Int8"]
        )
    )
)

And I want to convert these arrays, each with different functions, such as making them vectors of Symbol rather than String . I could mutate this dictionary directly, like this:

# Need to check these keys are present
if haskey(my_object, :foo)
    if haskey(my_object[:foo], :foo_items)
        my_object[:foo][:foo_items] = Symbol.(my_object[:foo][:foo_items])
    ...
end

This however quickly becomes very tedious, with lots of duplication, and is therefore error-prone. I was hoping to use aliasing to make this a bit simpler and more readable, especially because containers like Dict are passed by reference:

if haskey(my_object, :foo)
    foo = my_object[:foo]
    if haskey(foo, :foo_items)
        foo_items = foo[:foo_items]
        foo_items = Symbol.(foo_items)
    ...
end

This however does not work, with my_object remaining unchanged. Which is strange, because === implies that the memory addresses are the same up until the actual change is made:

julia> foo = my_object[:foo];
julia> foo === my_object[:foo]
true
julia> foo_items = foo[:foo_items];
julia> foo_items === my_object[:foo][:foo_items]
true

Is this a case of copy-on-write ? Why can't I mutate the dictionary this way? And what can I do instead if I want to mutate elements of a nested dictionary in a simpler way?

I would do it recursively

function conversion!(dict, keyset)
    for (k, v) in dict
        if v isa Dict
            conversion!(v, keyset)
        else
            if k in keyset
                dict[k] = converted(Val(k), v)
            end
        end
    end
end

converted(::Val{:foo_items}, value) = Symbol.(value)
# converted(::Val{:bar_name}, value) = ...

my_object = Dict{Symbol, Any}(
    :foo => Dict{Symbol, Any}(
        :foo_items => ["item_a", "item_b", "item_c"],
        :bar => Dict{Symbol, Any}(
            :bar_name => ["name_a", "name_b", "name_c"],
            :type_before => ["Float32", "Float64", "String"],
            :type_after => ["Int32", "Int64", "Int8"]
        )
    )
)

toconvert = Set([:foo_items])#, :bar_name, :type_before, :type_after])

@show my_object
conversion!(my_object, toconvert)
@show my_object
my_object = Dict{Symbol, Any}(:foo => Dict{Symbol, Any}(:foo_items => ["item_a", "item_b", "item_c"], :bar => Dict{Symbol, Any}(:type_before => ["Float32", "Float64", "String"], :bar_name => ["name_a", "name_b", "name_c"], :type_after => ["Int32", "Int64", "Int8"])))
my_object = Dict{Symbol, Any}(:foo => Dict{Symbol, Any}(:foo_items => [:item_a, :item_b, :item_c], :bar => Dict{Symbol, Any}(:type_before => ["Float32", "Float64", "String"], :bar_name => ["name_a", "name_b", "name_c"], :type_after => ["Int32", "Int64", "Int8"])))

Feel like the code may be more type-stable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM