Dictionary 具有自定义类型键的奇怪Dict行为

Dictionary 具有自定义类型键的奇怪Dict行为,dictionary,julia,Dictionary,Julia,我有一个递归函数,它利用一个全局dict来存储遍历树时已经获得的值。然而,至少一些存储在dict中的值似乎消失了!此简化代码显示了问题: type id level::Int32 x::Int32 end Vdict = Dict{id,Float64}() function getV(w::id) if haskey(Vdict,w) return Vdict[w] end if w.level == 12 retu

我有一个递归函数,它利用一个全局dict来存储遍历树时已经获得的值。然而,至少一些存储在dict中的值似乎消失了!此简化代码显示了问题:

type id
    level::Int32
    x::Int32
end

Vdict = Dict{id,Float64}()

function getV(w::id)
    if haskey(Vdict,w)
        return Vdict[w]
    end 
    if w.level == 12
        return 1.0
    end
    w.x == -111 && println("dont have: ",w)

    local vv = 0.0
    for j = -15:15
        local wj = id(w.level+1,w.x+j)
        vv += getV(wj)
    end
    Vdict[w] = vv
    w.x == -111 && println("just stored: ",w)
    vv
end

getV(id(0,0))
输出有许多行,如下所示:

just stored: id(11,-111)
dont have: id(11,-111)
just stored: id(11,-111)
dont have: id(11,-111)
just stored: id(11,-111)
dont have: id(11,-111)
...

我是否犯了愚蠢的错误,或者Julia的dict中是否存在错误?

默认情况下,自定义类型随对象标识的相等和散列实现一起出现。由于您的
id
类型是可变的,因此Julia是保守的,并假设您关心区分每个实例与另一个实例(因为它们可能会发生分歧):

Julia不知道您是否关心对象的长期行为或当前值

有两种方法可以使其按您的意愿运行:

  • 使自定义类型不可变。看起来您不需要改变
    Id
    的内容。解决这个问题最简单、最直接的方法是将其定义为
    不可变Id
    。现在
    Id(11,-111)
    Id(11,-111)
    的任何其他构造完全无法区分,因为它的值永远不会改变。作为奖励,你可能也会看到更好的表现

  • 如果您确实需要改变值,您也可以定义自己的
    =
    Base.hash的实现,以便它们只关心当前值:

    ==(a::Id, b::Id) = a.level == b.level && a.x == b.x
    Base.hash(a::Id, h::Uint) = hash(a.level, hash(a.x, h))
    
    正如@StefanKarpinski,这不是可变值的默认值“因为它可以很容易地将某个内容粘贴到dict中,然后对其进行变异并“丢失”。”也就是说,对象的散列值已更改,但字典基于其旧散列值将其存储在某个位置,现在您无法再通过键查找访问该键/值对。即使创建的第二个对象与第一个对象具有相同的原始属性,它也无法找到它,因为字典在找到哈希匹配后会检查相等性。查找该键的唯一方法是将其变回其原始值,或显式要求字典
    Base.rehash其内容


  • 在这种情况下,我强烈推荐选项1。

    另请参阅相关的Julia软件包:
    ==(a::Id, b::Id) = a.level == b.level && a.x == b.x
    Base.hash(a::Id, h::Uint) = hash(a.level, hash(a.x, h))