# Julia 编程基础（八）：如何在最合适的场景使用字典与集合？ ## 8.1 索引与迭代

### 8.1.1 索引与可索引对象

julia> Set(1)ERROR: MethodError: no method matching getindex(::Set{Int64}, ::Int64)# 省略了一些回显的内容。 julia> 

julia> applicable(getindex, (1,), 1)true julia> applicable(getindex, Set(1), 1)false julia> 

### 8.1.2 迭代与可迭代对象

julia> tuple3 = (10, 20, 30, 40, 50); julia> for e in tuple3           println(e)       end1020304050 julia> 

julia> applicable(iterate, (1,))true julia> applicable(iterate, Set(1))true julia> 

## 8.2 标准字典

### 8.2.1 规则与约束

Julia 中的所有原语类型、复合类型以及预定义的容器类型都有相应的hash方法和isequal方法可用。如果你要定义自己的数据类型，并且有可能会让它成为字典的键类型，那么我强烈建议你去显式地定义针对该类型的hash方法和isequal方法。至于具体原因，我在后面会讲到。

### 8.2.2 类型与实例化

Dict类型有两个类型参数，分别是代表键类型的K和代表值类型的V。这个类型的构造函数Dict是比较灵活的。首先，我们调用它时可以不传给它任何参数值，就像这样：

julia> Dict()Dict{Any,Any} with 0 entries julia> 

julia> Dict{Int64, String}()Dict{Int64,String} with 0 entries julia> 

julia> mutable struct MyKey           code::String           sn::Int128       end julia> key1 = MyKey("mykey", 1); key2 = MyKey("mykey", 1); julia> 

julia> dict1 = Dict{MyKey, Int64}()Dict{MyKey,Int64} with 0 entries julia> 

julia> dict1[key1] = 1010 julia> key1.sn = -1; dict1[key1]10 julia>

julia> dict1[key2]ERROR: KeyError: key MyKey("mykey", 1) not foundStacktrace:  getindex(::Dict{MyKey,Int64}, ::MyKey) at ./dict.jl:477  top-level scope at REPL:1 julia> 

julia> key1 = MyKey("mykey", 1); isequal(key1, key2)false julia> 

==(x, y) = x === y

julia> import Base.== julia> ==(x::MyKey, y::MyKey) = x.code == y.code && x.sn == y.sn== (generic function with 156 methods) julia> 

julia> key1 = MyKey("mykey", 1); key2 = MyKey("mykey", 1); julia> key1 == key2, isequal(key1, key2)(true, true) julia>

julia> dict1 = Dict{MyKey, Int64}();  julia> dict1[key1] = 10; dict1[key2]ERROR: KeyError: key MyKey("mykey", 1) not foundStacktrace:  getindex(::Dict{MyKey,Int64}, ::MyKey) at ./dict.jl:477  top-level scope at REPL:1 julia> 

julia> hash(key1) == hash(key2)false julia> 

hash(x::Any) = hash(x, zero(UInt))

julia> import Base.hash julia> hash(k::MyKey, h::UInt) = hash(k.sn, hash(k.code, h))hash (generic function with 56 methods) julia> 

julia> key1 = MyKey("mykey", 1); key2 = MyKey("mykey", 1); julia> dict1 = Dict{MyKey, Int64}();  julia> dict1[key1] = 10; dict1[key2]10 julia> 

julia> key1.sn = -1; dict1[key1]ERROR: KeyError: key MyKey("mykey", -1) not foundStacktrace:  getindex(::Dict{MyKey,Int64}, ::MyKey) at ./dict.jl:477  top-level scope at REPL:1 julia> 

julia> Dict([(1, "a"), (2, "b"), (3, "c")])Dict{Int64,String} with 3 entries:  2 => "b"  3 => "c"  1 => "a" julia> 

julia> Dict(1=>"a", 2=>"b", 3=>"c")Dict{Int64,String} with 3 entries:  2 => "b"  3 => "c"  1 => "a" julia> 

Pair类型的值都是可索引对象。其中的键的索引号总是1，而值的索引号总是2。同时，Pair类型的值也都是可迭代对象。但是这个意义就不大了。因为这类值最多只能包含两个元素值，基本上用不着迭代。

### 8.2.3 操作字典

#### 8.2.3.1 存取键值对

julia> dict2 = Dict{String, Int64}()Dict{String,Int64} with 0 entries julia> dict2["a"] = 11 julia> 

julia> dict2["a"]1 julia> dict2["b"]ERROR: KeyError: key "b" not found# 省略了一些回显的内容。 julia> 

julia> haskey(dict2, "a")true julia> haskey(dict2, "b")false julia>

julia> haskey(dict2, "a") ? dict2["a"] : nothing1 julia> 

< 第一个操作数 > ? < 第二个操作数 > : < 第三个操作数 >

julia> get(dict2, "a", nothing), get(dict2, "b", nothing)(1, nothing) julia> 

julia> get!(dict2, "b", 2)2 julia> dict2Dict{String,Int64} with 2 entries:  "b" => 2  "a" => 1 julia> 

julia> pop!(dict2, "b")2 julia> dict2Dict{String,Int64} with 1 entry:  "a" => 1 julia> 

julia> pop!(dict2, "b", 2)2 julia> dict2Dict{String,Int64} with 1 entry:  "a" => 1 julia> 

julia> pop!(dict2, "b")ERROR: KeyError: key "b" not found# 省略了一些回显的内容。 julia> 

julia> haskey(dict2, "b") ? dict2["b"] : dict2["b"]=22 julia> dict2Dict{String,Int64} with 2 entries:  "b" => 2  "a" => 1 julia> 

julia> haskey(dict2, "b") ? pop!(dict2, "b") : 22 julia> dict2Dict{String,Int64} with 1 entry:  "a" => 1 julia> 

julia> delete!(dict2, "a")Dict{String,Int64} with 0 entries julia> dict2Dict{String,Int64} with 0 entries julia> 

#### 8.2.3.2 迭代

julia> dict3 = Dict("a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5);julia> for p in dict3           println("$(p) =>$(p) [$(typeof(p))]") endc => 3 [Pair{String,Int64}]e => 5 [Pair{String,Int64}]b => 2 [Pair{String,Int64}]a => 1 [Pair{String,Int64}]d => 4 [Pair{String,Int64}] julia>  对于字典来说，迭代变量可以只有一个。这时，for语句迭代出的值的类型就将是Pair类型。而该类型的两个类型参数值将分别是字典的键类型和值类型。我们已经知道，一个Pair类型的值就代表着一个键值对，也相当于一个小容器。并且，这类值既属于可索引对象也属于可迭代对象。其中的键的索引号是1，而值的索引号则是2 这里的迭代变量也可以是两个。在这种情况下，我们必须使用圆括号把这两个变量的标识符包裹起来，就像这样： 复制代码 julia> for (k,v) in dict3 println("($k, $v) [$(typeof(k)), \$(typeof(v))]")       end(c, 3) [String, Int64](e, 5) [String, Int64](b, 2) [String, Int64](a, 1) [String, Int64](d, 4) [String, Int64] julia> 

#### 8.2.3.3 获取列表

Julia 提供了一些方法，可以让我们分别获取字典中的键列表和值列表。

julia> dict4 = Dict("a"=>1, "b"=>2, "c"=>3); julia> keys_dict4 = keys(dict4)Base.KeySet for a Dict{String,Int64} with 3 entries. Keys:  "c"  "b"  "a" julia> typeof(keys_dict4)Base.KeySet{String,Dict{String,Int64}} julia> 

keys方法在被调用之后会返回一个Base.KeySet类型的值。在这个值中就包含了字典中所有的键。我们可以看到，这个值的类型与源字典类型的关系非常紧密。实际上，这类值仅仅是把源字典又简单地包装了一下而已。

julia> in("a", keys_dict4)true julia> 

julia> in(Pair("a", 1), dict4)true julia> 

julia> Set(keys_dict4)Set(["c", "b", "a"]) julia> collect(keys_dict4)3-element Array{String,1}: "c" "b" "a" julia> 

julia> values_dict4 = values(dict4)Base.ValueIterator for a Dict{String,Int64} with 3 entries. Values:  3  2  1 julia> typeof(values_dict4)Base.ValueIterator{Dict{String,Int64}} julia> 

julia> Set(values_dict4)Set([2, 3, 1]) julia> collect(values_dict4)3-element Array{Int64,1}: 3 2 1 julia>

julia> keytype(dict4) == eltype(keys_dict4) == Stringtrue julia> valtype(dict4) == eltype(values_dict4) == Int64true julia> 

#### 8.2.3.4 合并

Julia 中有专门的函数可以进行字典的合并，名为merge。这里说的其实是两个函数，我们先来讲参数较少的那一个。

julia> merge(dict3, dict4)Dict{String,Int64} with 5 entries:  "c" => 3  "e" => 5  "b" => 2  "a" => 1  "d" => 4 julia> 

julia> dict3Dict{String,Int64} with 5 entries:  "c" => 3  "e" => 5  "b" => 2  "a" => 1  "d" => 4 julia> dict4Dict{String,Int64} with 3 entries:  "c" => 3  "b" => 2  "a" => 1 julia> dict5 = Dict("a"=>10, "b"=>20, "c"=>30); julia> merge(dict3, dict4, dict5)Dict{String,Int64} with 5 entries:  "c" => 30  "e" => 5  "b" => 20  "a" => 10  "d" => 4 julia> 

julia> dict6 = Dict("a"=>1.0, "d"=>4.0, "e"=>5.0); julia> merge(dict5, dict6)Dict{String,Float64} with 5 entries:  "c" => 30.0  "e" => 5.0  "b" => 20.0  "a" => 1.0  "d" => 4.0 julia> 

julia> dict7 = Dict(1=>"a", 2=>"b", 3=>"c"); dict8 = Dict(1.0=>'a', 3.0=>'c'); julia> merge(dict7, dict8)Dict{Float64,Any} with 3 entries:  2.0 => "b"  3.0 => 'c'  1.0 => 'a' julia> 

julia> merge(dict5, dict6)Dict{String,Float64} with 5 entries:  "c" => 30.0  "e" => 5.0  "b" => 20.0  "a" => 1.0  "d" => 4.0 julia> merge(+, dict5, dict6)Dict{String,Float64} with 5 entries:  "c" => 30.0  "e" => 5.0  "b" => 20.0  "a" => 11.0  "d" => 4.0 julia> 

julia> dict9 = Dict(1=>"x", 2=>"y", 4=>"z"); julia> merge!(dict9, dict7)Dict{Int64,String} with 4 entries:  4 => "z"  2 => "b"  3 => "c"  1 => "a" julia> merge!(*, dict9, dict8)Dict{Int64,String} with 4 entries:  4 => "z"  2 => "b"  3 => "cc"  1 => "aa" julia> dict9Dict{Int64,String} with 4 entries:  4 => "z"  2 => "b"  3 => "cc"  1 => "aa" julia> 

julia> merge!(dict9, dict8)ERROR: MethodError: Cannot convert an object of type Char to an object of type String# 省略了一些回显的内容。 julia> 

## 8.3 集合

julia> mock_set = Dict{String,Nothing}()Dict{String,Nothing} with 0 entries

julia> haskey(mock_set, "d") ? true : falsefalse julia> 

### 8.3.1 类型与实例化

julia> Set()Set(Any[]) julia> typeof(ans)Set{Any} julia> Set([1,2,4])Set([4, 2, 1]) julia> typeof(ans)Set{Int64} julia> Set(Dict(1=>"x", 2=>"y", 4=>"z"))Set(Pair{Int64,String}[2 => "y", 4 => "z", 1 => "x"]) julia> typeof(ans)Set{Pair{Int64,String}} julia> 

julia> Set((1,2,4))Set([4, 2, 1]) julia> typeof(ans)Set{Int64} julia> Set((1,2.0,"4"))Set(Any["4", 2.0, 1]) julia> typeof(ans)Set{Any} julia> 

### 8.3.2 操作集合

julia> union(Set([1,2]), Set([2,3]), Set([3,4,5]))Set([4, 2, 3, 5, 1]) julia> 

julia> intersect(Set([1,2]), Set([2,3]), Set([3,4,5]))Set(Int64[]) julia> intersect(Set([1,2,3]), Set([2,3,4]), Set([3,4,5]))Set() julia> 

julia> setdiff(Set([1,2,3]), Set([2,3,4]), Set([3,4,5]))Set() julia> 

julia> setdiff(Set([2,3,4]), Set([1,2,3]), Set([3,4,5]))Set(Int64[]) julia> setdiff(Set([3,4,5]), Set([2,3,4]), Set([1,2,3]))Set() julia> 

julia> symdiff(Set([1,2,3]), Set([2,3,4]))Set([4, 1]) julia> 

julia> symdiff(Set([1,2,3]), Set([2,3,4]), Set([3,4,5]))Set([3, 5, 1]) julia> 

julia> symdiff(symdiff(Set([1,2,3]), Set([2,3,4])), Set([3,4,5]))Set([3, 5, 1]) julia> 

julia> symdiff(Set([1,2,3]), Set([2,3,4]), Set([3,4,5]), Set([4,5,6]))Set([4, 3, 6, 1]) julia> 

julia> symdiff(Set([1,2,3]), Set([2,3,4]))Set([4, 1]) julia> symdiff(ans, Set([3,4,5]))Set([3, 5, 1]) julia> symdiff(ans, Set([4,5,6]))Set([4, 3, 6, 1]) julia> 

julia> methods(setdiff!)# 5 methods for generic function "setdiff!": setdiff!(s1::BitSet, s2::BitSet) in Base at bitset.jl:320 setdiff!(v::AbstractArray{T,1} where T, itrs...) in Base at array.jl:2406 setdiff!(s::Set, t::Set) in Base at set.jl:78 setdiff!(s::AbstractSet, itr) in Base at abstractset.jl:171 setdiff!(s::AbstractSet, itrs...) in Base at abstractset.jl:165 julia> 

## 8.4 通用操作

empty!函数还有一个名为empty的孪生函数。这个函数不会对参数值进行任何的修改，而且会返回一个与参数值一模一样但不包含任何元素值的结果值。另外，还有一个名称很相近的函数isempty，它可以判断一个容器是否为空。

## 8.5 小结

Julia 编程基础（一）：初识 Julia，除了性能堪比 C 语言还有哪些特性？

Julia 编程基础（二）：开发 Julia 项目必须掌握的预备知识

Julia 编程基础（三）：一文掌握 Julia 的变量与常量

Julia 编程基础（四）：如何用三个关键词搞懂 Julia 类型系统

Julia 编程基础（五）：数值与运算

Julia 编程基础（六）：玩转字符和字符串

Julia 编程基础（七）：由浅入深了解参数化类型
{"ft_lang_label":"__label__zh","ft_lang_prob":0.92456216,"math_prob":0.9633559,"size":26863,"snap":"2019-51-2020-05","text_gpt3_token_len":20426,"char_repetition_ratio":0.122677684,"word_repetition_ratio":0.16111414,"special_character_ratio":0.2402189,"punctuation_ratio":0.15258712,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9890042,"pos_list":[0,1,2],"im_url_duplicate_count":[null,4,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2019-12-16T08:56:19Z\",\"WARC-Record-ID\":\"<urn:uuid:66f5d673-abbe-4be2-8b6e-4d0a5f0a0185>\",\"Content-Length\":\"288995\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:9e8a5d44-4a90-41b1-8e96-286800db9bbd>\",\"WARC-Concurrent-To\":\"<urn:uuid:ad86ee95-2e86-4e0a-b41f-8b891440bd2f>\",\"WARC-IP-Address\":\"39.105.227.169\",\"WARC-Target-URI\":\"https://www.infoq.cn/article/AgORsx2itWhFidwQ7tj6\",\"WARC-Payload-Digest\":\"sha1:IZNN7ERKTPFEDEZQNVHXXOMSAFZXKUK5\",\"WARC-Block-Digest\":\"sha1:FSU4HFDE46MGNA273AGUNSCN3SMYYZN5\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2019/CC-MAIN-2019-51/CC-MAIN-2019-51_segments_1575541318556.99_warc_CC-MAIN-20191216065654-20191216093654-00287.warc.gz\"}"}