Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable dictionary

Is there a way to enforce a dictionary being constant?

I have a function which reads out a file for parameters (and ignores comments) and stores it in a dict:

function getparameters(filename::AbstractString)
    f = open(filename,"r")
    dict = Dict{AbstractString, AbstractString}()
    for ln in eachline(f)
        m = match(r"^\s*(?P<key>\w+)\s+(?P<value>[\w+-.]+)", ln) 
        if m != nothing
            dict[m[:key]] = m[:value]
        end
    end
    close(f)
    return dict
end

This works just fine. Since i have a lot of parameters, which i will end up using on different places, my idea was to let this dict be global. And as we all know, global variables are not that great, so i wanted to ensure that the dict and its members are immutable.

Is this a good approach? How do i do it? Do i have to do it?


Bonus answerable stuff :)

Is my code even ok? (it is the first thing i did with julia, and coming from c/c++ and python i have the tendencies to do things differently.) Do i need to check whether the file is actually open? Is my reading of the file "julia"-like? I could also readall and then use eachmatch. I don't see the "right way to do it" (like in python).

like image 615
hr0m Avatar asked Jul 15 '16 17:07

hr0m


3 Answers

Why not use an ImmutableDict? It's defined in base but not exported. You use one as follows:

julia> id = Base.ImmutableDict("key1"=>1)
Base.ImmutableDict{String,Int64} with 1 entry:
  "key1" => 1

julia> id["key1"]
1

julia> id["key1"] = 2
ERROR: MethodError: no method matching setindex!(::Base.ImmutableDict{String,Int64}, ::Int64, ::String)
 in eval(::Module, ::Any) at .\boot.jl:234
 in macro expansion at .\REPL.jl:92 [inlined]
 in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at .\event.jl:46

julia> id2 = Base.ImmutableDict(id,"key2"=>2)
Base.ImmutableDict{String,Int64} with 2 entries:
  "key2" => 2
  "key1" => 1

julia> id.value
1

You may want to define a constructor which takes in an array of pairs (or keys and values) and uses that algorithm to define the whole dict (that's the only way to do so, see the note at the bottom).


Just an added note, the actual internal representation is that each dictionary only contains one key-value pair, and a dictionary. The get method just walks through the dictionaries checking if it has the right value. The reason for this is because arrays are mutable: if you did a naive construction of an immutable type with a mutable field, the field is still mutable and thus while id["key1"]=2 wouldn't work, id.keys[1]=2 would. They go around this by not using a mutable type for holding the values (thus holding only single values) and then also holding an immutable dict. If you wanted to make this work directly on arrays, you could use something like ImmutableArrays.jl but I don't think that you'd get a performance advantage because you'd still have to loop through the array when checking for a key...

like image 80
Chris Rackauckas Avatar answered Sep 17 '22 22:09

Chris Rackauckas


First off, I am new to Julia (I have been using/learning it since only two weeks). So do not put any confidence in what I am going to say unless it is validated by others.

The dictionary data structure Dict is defined here

julia/base/dict.jl

There is also a data structure called ImmutableDict in that file. However as const variables aren't actually const why would immutable dictionaries be immutable?

The comment states:

ImmutableDict is a Dictionary implemented as an immutable linked list, which is optimal for small dictionaries that are constructed over many individual insertions Note that it is not possible to remove a value, although it can be partially overridden and hidden by inserting a new value with the same key

So let us call what you want to define as a dictionary UnmodifiableDict to avoid confusion. Such object would probably have

  • a similar data structure as Dict.
  • a constructor that takes a Dict as input to fill its data structure.
  • specialization (a new dispatch?) of the the method setindex! that is called by the operator [] = in order to forbid modification of the data structure. This should be the case of all other functions that end with ! and hence modify the data.

As far as I understood, It is only possible to have subtypes of abstract types. Therefore you can't make UnmodifiableDict as a subtype of Dict and only redefine functions such as setindex!

Unfortunately this is a needed restriction for having run-time types and not compile-time types. You can't have such a good performance without a few restrictions.

Bottom line:

The only solution I see is to copy paste the code of the type Dict and its functions, replace Dict by UnmodifiableDict everywhere and modify the functions that end with ! to raise an exception if called.

you may also want to have a look at those threads.

  • https://groups.google.com/forum/#!topic/julia-users/n-lqjybIO_w
  • https://github.com/JuliaLang/julia/issues/1974
like image 35
Issam T. Avatar answered Sep 18 '22 22:09

Issam T.


REVISION

Thanks to Chris Rackauckas for pointing out the error in my earlier response. I'll leave it below as an illustration of what doesn't work. But, Chris is right, the const declaration doesn't actually seem to improve performance when you feed the dictionary into the function. Thus, see Chris' answer for the best resolution to this issue:

D1 = [i => sind(i) for i = 0.0:5:3600];
const D2 = [i => sind(i) for i = 0.0:5:3600];

function test(D)
    for jdx = 1:1000
        # D[2] = 2
        for idx = 0.0:5:3600
            a = D[idx]
        end     
    end
end

## Times given after an initial run to allow for compiling
@time test(D1); # 0.017789 seconds (4 allocations: 160 bytes)
@time test(D2); # 0.015075 seconds (4 allocations: 160 bytes)

Old Response

If you want your dictionary to be a constant, you can use:

const MyDict = getparameters( .. )

Update Keep in mind though that in base Julia, unlike some other languages, it's not that you cannot redefine constants, instead, it's just that you get a warning when doing so.

julia> const a = 2
2

julia> a = 3
WARNING: redefining constant a
3

julia> a
3

It is odd that you don't get the constant redefinition warning when adding a new key-val pair to the dictionary. But, you still see the performance boost from declaring it as a constant:

D1 = [i => sind(i) for i = 0.0:5:3600];
const D2 = [i => sind(i) for i = 0.0:5:3600];

function test1()
    for jdx = 1:1000
        for idx = 0.0:5:3600
            a = D1[idx]
        end     
    end
end


function test2()
    for jdx = 1:1000
        for idx = 0.0:5:3600
            a = D2[idx]
        end     
    end
end

## Times given after an initial run to allow for compiling
@time test1(); # 0.049204 seconds (1.44 M allocations: 22.003 MB, 5.64% gc time)
@time test2(); # 0.013657 seconds (4 allocations: 160 bytes)
like image 28
Michael Ohlrogge Avatar answered Sep 17 '22 22:09

Michael Ohlrogge