使用自定义类型的键的奇怪Dict行为

时间:2015-07-16 15:47:23

标签: dictionary julia

我有一个递归函数,它利用全局字典来存储遍历树时已经获得的值。但是,至少存储在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)
...

我有一个愚蠢的错误,或者朱莉娅的咒语中是否有错误?

1 个答案:

答案 0 :(得分:6)

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

julia> type Id # There's a strong convention to capitalize type names in Julia
           level::Int32
           x::Int32
       end

julia> x = Id(11, -111)
       y = Id(11, -111)
       x == y
false

julia> x.level = 12; (x,y)
(Id(12,-111),Id(11,-111))

朱莉娅并不知道你是否关心对象的长期行为或其当前价值。

有两种方法可以让您按照自己的喜好行事:

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

  2. 如果您确实需要改变值,您可以选择定义自己的==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 just pointed out on the mailing list,这不是可变值&#34;的默认值,因为它可以很容易地在dict中粘贴某些内容,然后将其变异,并且&#39丢失它&#39; <#em>也就是说,对象的哈希值已经改变,但字典根据其旧的哈希值将其存储在一个地方,现在你再也无法访问了通过键查找键/值对。即使你创建了第二个具有相同原始属性的对象,它也无法找到它,因为字典在找到哈希匹配后检查相等性。查找该密钥的唯一方法是将其变回原始值或明确要求字典Base.rehash!其内容。

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