How to use haskey () and in () functions with complex dictionaries in Julia?
The haskey () and in () functions are very useful for checking the contents of dictionaries in Julia:
julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5)
Dict{String,Int64} with 5 entries:
"c" => 3
"e" => 5
"b" => 2
"a" => 1
"d" => 4
julia> haskey(dict, "a")
true
julia> in(("a" => 1), dict)
true
but I was surprised by their behavior with complex keys:
julia> immutable MyT
A::String
B::Int64
end
julia> a = Dict(MyT("Tom",191)=>1,MyT("Bob",20)=>1,MyT("Jo",315)=>1,MyT("Luc",493)=>1)
Dict{MyT,Int64} with 4 entries:
MyT("Tom",191) => 1
MyT("Jo",315) => 1
MyT("Bob",20) => 1
MyT("Luc",493) => 1
julia> keys(a)
Base.KeyIterator for a Dict{MyT,Int64} with 4 entries. Keys:
MyT("Tom",191)
MyT("Jo",315)
MyT("Bob",20)
MyT("Luc",493)
julia> haskey(a, MyT("Tom",191))
false
julia> in((MyT("Tom",191) => 1), a)
false
What have I done wrong? Thanks a lot for your comments!
Thanks to @Michael K. Borregaard, I can suggest this solution:
a = Dict{MyT, Int64}()
keyArray = Array{MyT,1}()
keyArray = [MyT("Tom",191),MyT("Bob",20),MyT("Jo",315),MyT("Luc",493)]
for i in keyArray
a[i] = 1
end
println(a)
# Dict(MyT("Tom",191)=>1,MyT("Tom",191)=>1,MyT("Luc",493)=>1,MyT("Jo",315)=>1,MyT("Luc",493)=>1,MyT("Bob",20)=>1,MyT("Jo",315)=>1,MyT("Bob",20)=>1)
keyArray[1] # MyT("Tom",191)
haskey(a, keyArray[1]) # true
But I have to store the keys in a separate array. This means it cannot guarantee the unity of the keys which are the power of dictionaries and why I chose to use it :(
So I have to use another step:
unique(keyArray)
Another better solution:
function CompareKeys(k1::MyT, k2::MyT)
if k1.A == k2.A && k1.B == k2.B
return true
else
return false
end
end
function ExistKey(k::MyT, d::Dict{MyT, Int64})
for i in keys(d)
if CompareKeys(k, i)
return true
end
end
return false
end
a = Dict(MyT("Tom",191)=>1,MyT("Bob",20)=>1,MyT("Jo",315)=>1,MyT("Luc",493)=>1)
ExistKey(MyT("Tom",192),a) # false
ExistKey(MyT("Tom",191),a) # true
Compared to Julia, Go is simpler for this problem:
package main
import (
"fmt"
)
type MyT struct {
A string
B int
}
func main() {
dic := map[MyT]int{MyT{"Bob", 10}: 1, MyT{"Jo", 21}: 1}
if _, ok := dic[MyT{"Bob", 10}]; ok {
fmt.Println("key exists")
}
}
// answer is "key exists"
source to share
You just need to teach your type MyT
so that you will consider it equal in terms of its composite fields:
julia> immutable MyT
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(x.A, hash(x.B, hash(0x7d6979235cb005d0, h)))
julia> a = Dict(MyT("Tom",191)=>1,MyT("Bob",20)=>1,MyT("Jo",315)=>1,MyT("Luc",493)=>1)
Dict{MyT,Int64} with 4 entries:
MyT("Jo", 315) => 1
MyT("Luc", 493) => 1
MyT("Tom", 191) => 1
MyT("Bob", 20) => 1
julia> haskey(a, MyT("Tom",191))
true
julia> in((MyT("Tom",191) => 1), a)
true
source to share
There are many good answers here, I just wanted to add some subtlety: this is partly because it ==
calls ===
instead of recursively ==
when checking for structural equality, and partly because equal to ( ==
) strings are not currently identical to ( ===
). In particular, the fact that MyT("foo", 1) != MyT("foo", 1)
is that "foo" !== "foo"
.
Strings are only "immutable by convention" - they are technically mutable, but Julia does not disclose the API for mutating them and does not recommend mutating them. However, you can access and modify your base bytes, which allows you to write a program that allocates two lines by changing them rather than the other. This means that they cannot be ===
in the Henry Baker sense of the predicate "EGAL" (also here ). If you have an immutable type with only "primitive type" fields, this doesn't happen:
julia> immutable MyT2 # `struct MyT2` in 0.6
A::Float64
B::Int
end
julia> x = MyT2(1, 1)
MyT2(1.0, 1)
julia> y = MyT2(1, 1)
MyT2(1.0, 1)
julia> x == y
true
julia> x === y
true
I have already suggested that we change this and ==
recursively call ==
. This has to be fixed, someone just has to get the job done. Moreover, in Julia 1.0, we could make Strings truly immutable, rather than just immutable by convention and therefore have meaning "foo" === "foo"
. I created an issue to discuss and track this change.
source to share
You are creating a new object in the call haskey
. But the two objects created MyT("Tom", 191)
are just two different MyT objects with the same field values.
Instead, do
key1 = MyT("Tom", 191)
a = Dict(key1 => 1)
haskey(a, key1)
see also
key2 = MyT("Tom", 191)
key1 == key2 # false
A jela-ideomatic way of dealing with this would be to define a method ==
for MyT objects, so two objects are equal if they have the same field values. This will allow you to use them the way you do.
It depends on whether you want a type to be complex. Another simple and efficient way to do what you want is to use Tuple as a key:
a = Dict(("Tom", 191) => 1)
haskey(a, ("Tom", 191)) # true
a[("Tom", 191)] # 1
source to share
My approach will be similar to Matt, but a little simpler (?). Tuples are perfectly valid dictionary keys, so I would simply overload the appropriate functions to convert your type back and forth to a tuple:
julia> immutable M; A::String; B::Int64; end julia> import Base: =>, haskey, in julia> =>(a::M, b) = (a.A, a.B)=>b julia> haskey(a::Dict, b::M) = haskey(a, (b.A, b.B)) julia> in(a::Pair{M, Int64}, b::Int64) = in((a.first.A,a.first.B)=>a.second,b)
julia> a = Dict(M("Dick", 10)=>1, M("Harry", 20)=>2) Dict{Tuple{String,Int64},Int64} with 2 entries: ("Dick", 10) => 1 ("Harry", 20) => 2 julia> haskey(a, M("Dick", 10)) true julia> in(M("Dick", 10)=>1, a) true
"Compared to Julia, Go is more direct about this problem."
True. It also tends to be more error prone (depending on your perspective). If you want to distinguish between two objects (used as keys) that do not correspond to the same object in memory, then Go's approach of simply testing "value equality" would get you in trouble here (although one could assert "value equality" usually makes sense when comparing "keys").
source to share