问题描述
在GO中,当我将结构用作地图的键时,键是唯一的.
In GO when I use a struct as a key for a map, there is an unicity of the keys.
例如,以下代码生成仅具有一个键的地图:map [{x 1}:1]
For example, the following code produce a map with only one key : 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]
我试图在朱莉娅(Julia)中做同样的事情,但我感到很奇怪:
I Tried to do the same in Julia and I had a strange surprise :
此Julia代码类似于GO代码,可生成具有10个键的字典!
This Julia code, similar to the GO one, produces a dictionary whith 10 keys !
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)]
那我做错了什么?
感谢@DanGetz提供解决方案! :
Thank you @DanGetz for the solution ! :
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)]
推荐答案
可变值在Julia中通过身份哈希 ,因为如果没有其他关于类型表示的知识,就无法知道两个值是否与相同的结构意味着相同或不同.如果在将值用作字典键之后对值进行突变,则按值对可变对象进行哈希处理可能会特别成问题.按身份哈希时,这并不是问题,因为可变对象的身份即使修改后也保持不变.另一方面,按值对不可变对象进行散列是绝对安全的-因为它们不能被突变,因此,这是不可变类型的默认行为.在给定的示例中,如果将MyT
设为不可变,则将自动获得您期望的行为:
Mutable values hash by identity in Julia, since without additional knowledge about what a type represents, one cannot know if two values with the same structure mean the same thing or not. Hashing mutable objects by value can be especially problematic if you mutate a value after using it as a dictionary key – this is not a problem when hashing by identity since the identity of a mutable object remains the same even when it is modified. On the other hand, it's perfectly safe to hash immutable objects by value – since they cannot be mutated, and accordingly that is the default behavior for immutable types. In the given example, if you make MyT
immutable you will automatically get the behavior you're expecting:
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
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)
对于要用作哈希键的包含String
和Int
值的类型,不变性可能是正确的选择.实际上,不变性通常是正确的选择,这就是为什么引入结构类型的关键字在不可变结构中更改为0.6到struct
,在可变结构中更改为mutable struct
的原因-人们会追求首先使用更短,更简单的名称,因此应该是更好的默认选择-即不变性.
For a type holding a String
and an Int
value that you want to use as a hash key, immutability is probably the right choice. In fact, immutability is the right choice more often than not, which is why the keywords introducing structural types has been change in 0.6 to struct
for immutable structures and mutable struct
for mutable structures – on the principle that people will reach for the shorter, simpler name first, so that should be the better default choice – i.e. immutability.
正如@ntdef所写,您可以通过重载Base.hash
函数来更改您的类型的哈希行为.但是,他的定义在某些方面是不正确的(这可能是我们的错误,因为未能更清晰,更彻底地记录下来).
As @ntdef has written, you can change the hashing behavior of your type by overloading the Base.hash
function. However, his definition is incorrect in a few respects (which is probably our fault for failing to document this more prominently and thoroughly):
- 要重载的
Base.hash
的方法签名是Base.hash(::T, ::UInt)
. -
Base.hash(::T, ::UInt)
方法必须返回UInt
值. - 如果要重载
Base.hash
,还应该重载Base.==
才能匹配.
- The method signature of
Base.hash
that you want to overload isBase.hash(::T, ::UInt)
. - The
Base.hash(::T, ::UInt)
method must return aUInt
value. - If you are overloading
Base.hash
, you should also overloadBase.==
to match.
因此,这将是按值使可变类型散列的正确方法(重新定义MyT
需要新的Julia会话):
So this would be a correct way to make your mutable type hash by value (new Julia session required to redefine 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
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)
这很烦人,但是 AutoHashEquals 软件包可以自动完成此操作,枯燥乏味.您需要做的就是在type
定义前面加上@auto_hash_equals
宏:
This is kind of annoying to do manually, but the AutoHashEquals package automates this, taking the tedium out of it. All you need to do is prefix the type
definition with the @auto_hash_equals
macro:
using AutoHashEquals
@auto_hash_equals type MyT # `@auto_hash_equals mutable struct MyT` in 0.6
A::String
B::Int64
end
底线:
-
如果您的类型应具有基于值的相等性和散列,请认真考虑使其变为不可变的.
If you have a type that should have value-based equality and hashing, seriously consider making it immutable.
如果您的类型确实必须是可变的,那么请认真考虑将其用作哈希键是否是个好主意.
If your type really has to be mutable, then think hard about whether it's a good idea to use as a hash key.
如果您确实需要使用可变类型作为具有基于值的相等性和哈希语义的哈希键,请使用AutoHashEquals
包.
If you really need to use a mutable type as a hash key with value-based equality and hashing semantics, use the AutoHashEquals
package.
这篇关于Go而不是Julia复杂的关键词典的唯一性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!