Go中复杂关键词典的单一性而不是朱莉娅?

Fre*_*red 5 struct dictionary types julia

在GO中,当我使用结构作为地图的键时,键是一个单一的.

例如,以下代码生成仅包含一个键的映射:map [{x 1}:1]

package main

import (
    "fmt"
)

type MyT struct {
    A string
    B int
}

func main() {

    dic := make(map[MyT]int)

    for i := 1; i <= 10; i++ {
        dic[MyT{"x", 1}] = 1
    }

    fmt.Println(dic)
}

// result : map[{x 1}:1]
Run Code Online (Sandbox Code Playgroud)

我试图在朱莉娅做同样的事情,我有一个奇怪的惊喜:

这个Julia代码,类似于GO代码,生成一个带有10个键的字典!

    type MyT
        A::String
        B::Int64
    end

    dic = Dict{MyT, Int64}()

    for i in 1:10
        dic[MyT("x", 1)] = 1
    end

    println(dic)
    # Dict(MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1,MyT("x",1)=>1)

    println(keys(dic))
    # MyT[MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1),MyT("x",1)]
Run Code Online (Sandbox Code Playgroud)

那我做错了什么?

谢谢@DanGetz的解决方案!:

immutable MyT     # or struct MyT with julia > 0.6
    A::String
    B::Int64
end

dic = Dict{MyT, Int64}()

for i in 1:10
    dic[MyT("x", 1)] = 1
end

println(dic)         # Dict(MyT("x", 1)=>1)
println(keys(dic))   # MyT[MyT("x", 1)]
Run Code Online (Sandbox Code Playgroud)

Ste*_*ski 8

可变值在Julia中按标识哈希,因为没有关于类型表示什么的额外知识,人们不能知道具有相同结构的两个值是否意味着相同或不同.如果在将值用作字典键之后改变值,则按值散列可变对象可能会特别成问题 - 当通过标识进行散列时这不是问题,因为即使修改了可变对象的标识也保持相同.另一方面,按值散列不可变对象是完全安全的 - 因为它们不能被变异,因此这是不可变类型的默认行为.在给定的示例中,如果您创建MyT不可变,您将自动获得您期望的行为:

immutable MyT # `struct MyT` in 0.6
    A::String
    B::Int64
end

dic = Dict{MyT, Int64}()

for i in 1:10
    dic[MyT("x", 1)] = 1
end
Run Code Online (Sandbox Code Playgroud)

julia> dic
Dict{MyT,Int64} with 1 entry:
  MyT("x", 1) => 1

julia> keys(dic)
Base.KeyIterator for a Dict{MyT,Int64} with 1 entry. Keys:
  MyT("x", 1)
Run Code Online (Sandbox Code Playgroud)

对于包含a StringInt要用作哈希键的值的类型,不可变性可能是正确的选择.事实上,不变性通常是正确的选择,这就是为什么引入结构类型的关键字在0.6中变为struct不可变结构和mutable struct可变结构的原因 - 原则上人们将首先寻找更短,更简单的名称,所以这应该是更好的默认选择 - 即不变性.

正如@ntdef编写的那样,您可以通过重载Base.hash函数来更改类型的散列行为.但是,他的定义在某些方面是不正确的(这可能是我们未能更加突出和彻底地记录这一点的错误):

  1. Base.hash您想要重载的方法签名是Base.hash(::T, ::UInt).
  2. Base.hash(::T, ::UInt)方法必须返回一个UInt值.
  3. 如果你正在超载Base.hash,你也应该重载Base.==以匹配.

所以这将是一个正确的方法来按值生成可变类型哈希(重新定义所需的新Julia会话MyT):

type MyT # `mutable struct MyT` in 0.6
    A::String
    B::Int64
end

import Base: ==, hash

==(x::MyT, y::MyT) = x.A == y.A && x.B == y.B

hash(x::MyT, h::UInt) = hash((MyT, x.A, x.B), h)

dic = Dict{MyT, Int64}()

for i in 1:10
    dic[MyT("x", 1)] = 1
end
Run Code Online (Sandbox Code Playgroud)

julia> dic
Dict{MyT,Int64} with 1 entry:
  MyT("x", 1) => 1

julia> keys(dic)
Base.KeyIterator for a Dict{MyT,Int64} with 1 entry. Keys:
  MyT("x", 1)
Run Code Online (Sandbox Code Playgroud)

手动操作有点烦人,但AutoHashEquals软件包会自动执行此操作,从中解脱出来.您需要做的就是type@auto_hash_equals宏定义前加上定义:

using AutoHashEquals

@auto_hash_equals type MyT # `@auto_hash_equals mutable struct MyT` in 0.6
    A::String
    B::Int64
end
Run Code Online (Sandbox Code Playgroud)

底线:

  • 如果您的类型应该具有基于值的相等性和散列,请认真考虑使其不可变.

  • 如果您的类型确实必须是可变的,那么请仔细考虑将其用作哈希键是否是个好主意.

  • 如果您确实需要使用可变类型作为具有基于值的相等性和散列语义的散列键,请使用该AutoHashEquals包.