不可变字典

7

有没有方法可以强制一个字典保持不变?

我有一个函数,它从文件中读取参数(并忽略注释),然后将其存储在一个字典中:

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

这很好用。由于我有很多参数,将在不同的地方使用,所以我的想法是让这个字典成为全局的。众所周知,全局变量并不是那么好,所以我想确保字典及其成员是不可变的。
这是一个好方法吗?我该怎么做?我必须这样做吗?
额外可回答的问题:
我的代码是否正确?(这是我用Julia编写的第一件事情,来自C/C++和Python,我有不同的做事方式。)我需要检查文件是否已经打开吗?我的读取文件的方式符合Julia的风格吗?我也可以使用readall然后使用eachmatch。我没有看到“正确的做法”(就像在Python中)。

2
通常认为使用 open(filename) do f ... end 语法(参见 http://docs.julialang.org/en/release-0.4/manual/functions/#do-block-syntax-for-function-arguments)比显式地 close 文件更好。首先,使用 do 块即使发生异常也会清理文件。 - Fengyang Wang
4个回答

5
为什么不使用ImmutableDict?它在基础库中定义但未导出。您可以按以下方式使用它:
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

你可能想定义一个构造函数,该函数接受一组成对的数组(或键和值),并使用该算法来定义整个字典(这是唯一的方法,请参见底部的注释)。
仅附加一条说明,实际内部表示是每个字典仅包含一个键值对和一个字典。get方法只需通过检查字典是否具有正确值即可遍历字典。之所以这样做是因为数组是可变的:如果使用可变字段构建不可变类型,则该字段仍然是可变的。因此,id [“key1”] = 2 不起作用,但 id.keys [1] = 2 可以。他们通过不使用可变类型来保存值(因此仅保存单个值),然后再保存一个不可变字典来解决这个问题。如果要直接在数组上使用它,请使用ImmutableArrays.jl之类的东西,但我认为你不会获得性能优势,因为在检查键时仍然必须循环遍历数组...

在0.4.6版本中,我遇到了“ImmutableDict未定义”的问题! - Andy Hayden
Base.ImmutableDict? - Chris Rackauckas
带有Base.ImmutableDict(“key1”=>1) - Andy Hayden
那么它必须是v0.5及以上版本。它没有被导出(也没有文档),因此不能保证它会保留下来。 - Chris Rackauckas

3
除了现有的答案之外,如果你喜欢不可变性,并且想要得到性能高(但仍然是持久的)可以改变和扩展字典的操作,请查看 FunctionalCollections.jlPersistentHashMap 类型。
如果你想最大化性能并充分利用不可变性,而且你不打算对字典进行任何操作,考虑实现基于完美哈希函数的字典。事实上,如果你的字典是一个编译时常量,这些甚至可以提前计算(使用元编程)和预编译。

3

首先,我是Julia的新手(我只学习和使用它两个星期)。因此,除非其他人验证,否则请不要对我说的话抱有任何信心。

这里定义了字典数据结构Dict

julia/base/dict.jl

在该文件中还有一种名为ImmutableDict的数据结构。然而,由于const变量实际上并不是常量,为什么不可变字典会是不可变的呢?
注释说明如下:
ImmutableDict是作为不可变链表实现的字典,这对于通过许多单独的插入构建的小型字典是最优的。请注意,无法删除值,但可以通过插入具有相同键的新值来部分覆盖和隐藏该值。
因此,让我们将您要定义为字典的内容称为UnmodifiableDict,以避免混淆。这样的对象可能具有以下特点:
  • Dict具有类似的数据结构。
  • 一个构造函数,它以Dict作为输入来填充其数据结构。
  • setindex!方法的专门化(一个新的调度?),该方法由操作符[] =调用,以禁止修改数据结构。这应该是所有其他以!结尾且修改数据的函数的情况。
据我所理解,只有抽象类型的子类型才是可能的。因此,您无法将UnmodifiableDict作为Dict的子类型,并且只能重新定义函数,例如setindex!
不幸的是,这是需要限制以获得运行时类型而不是编译时类型。没有一些限制就无法拥有如此出色的性能。
底线是:
我唯一看到的解决方案是复制粘贴类型Dict及其函数的代码,将Dict替换为UnmodifiableDict并修改以!结尾的函数,以在调用时引发异常。
您还可以查看这些主题。

3

修订

感谢Chris Rackauckas指出我之前回答中的错误。我将保留下面的内容,以说明不起作用的内容。但是,Chris是正确的,在将字典输入函数时,const声明似乎并没有实际提高性能。因此,请参考Chris的答案,以解决此问题:

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)

旧响应

如果你想让你的字典是一个常量,你可以使用以下方式:

const MyDict = getparameters( .. )

更新 请记住,在基础的Julia中,与其他一些语言不同,您并不是无法重新定义常量,而只是在这样做时会收到警告。

julia> const a = 2
2

julia> a = 3
WARNING: redefining constant a
3

julia> a
3

添加新的键值对到字典时,不会出现常量重新定义警告,这很奇怪。但是,如果将其声明为常量,仍然可以看到性能提升:

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)

但实际上并不是这样的。我仍然可以使用MyDict[key] = value添加新的键值对,这正是我想要禁止的。 - hr0m
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Michael Ohlrogge
2
那个性能提升并不是真的。这是因为你在全局范围内运行它,但由于const的静态类型,所以它在函数中重新运行测试。Const与可变性不同,因为它实际上告诉编译器你不会改变类型,而不是值。因此,作为真正不可变结构的好答案,它将无法更改并且可以基于此获得更好的性能(这与糟糕的作用域选择无关)。 - Chris Rackauckas
2
没问题。理解REPL在全局范围内的作用以及它与编译器的交互方式并不是太直观。开发人员知道这会给新手带来性能问题,并且很难解释。这是开发人员仍在讨论的重大问题之一。 - Chris Rackauckas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接