跳转到内容

介绍 Julia/字典和集合

来自维基教科书,开放世界中的开放书籍
Previous page
函数
介绍 Julia Next page
字符串和字符
字典和集合

到目前为止介绍的许多函数都已在数组(和元组)上运行。但是数组只是集合类型之一。Julia 还有其他类型。

一个简单的查找表是组织多种类型数据的有用方式:给定一个单一的信息,例如数字、字符串或符号,称为**键**,相应的**值**是什么?为此,Julia 提供了 Dictionary 对象,简称 Dict。它是一种“关联集合”,因为它将键与值关联。

创建字典

[编辑 | 编辑源代码]

您可以使用以下语法创建一个简单的字典

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

dict 现在是一个字典。键是“a”、“b”和“c”,相应的 value 是 1、2 和 3。=> 运算符称为 Pair() 函数。在字典中,键总是唯一的 - 您不能有两个具有相同名称的键。

如果您事先知道键和值的类型,您可以在 Dict 关键字之后用大括号指定它们(并且可能应该这样做)

julia> dict = Dict{String,Integer}("a"=>1, "b" => 2)
Dict{String,Integer} with 2 entries:
 "b" => 2
 "a" => 1

您还可以使用生成器/ 推导 语法来创建字典

julia> dict = Dict(string(i) => sind(i) for i = 0:5:360)
Dict{String,Float64} with 73 entries:
 "320" => -0.642788
 "65"  => 0.906308
 "155" => 0.422618
 ⋮     => ⋮

使用以下语法创建一个类型化的空字典

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

或者您可以省略类型,并获得一个非类型化的字典

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

使用 for 循环创建字典条目有时很有用

files = ["a.txt", "b.txt", "c.txt"]
fvars = Dict()
for (n, f) in enumerate(files)
   fvars["x_$(n)"] = f
end

这是创建存储在字典中的“变量”集的一种方法

julia> fvars
Dict{Any,Any} with 3 entries:
 "x_1" => "a.txt"
 "x_2" => "b.txt"
 "x_3" => "c.txt"

查找内容

[编辑 | 编辑源代码]

要获取值,如果您有键

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5)

julia> dict["a"]
1

如果键是字符串。或者,如果键是符号

julia> symdict = Dict(:x => 1, :y => 3, :z => 6)
Dict{Symbol,Int64} with 3 entries:
 :z => 6
 :x => 1
 :y => 3
julia> symdict[:x]
1

或者如果键是整数

julia> intdict = Dict(1 => "one", 2 => "two", 3  => "three")
Dict{Int64,String} with 3 entries:
 2 => "two"
 3 => "three"
 1 => "one"
julia> intdict[2]
"two"

您可以使用 get() 函数,并在特定键没有值的情况下提供一个安全默认值

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5)
julia> get(dict, "a", 0)
1

julia> get(dict, "Z", 0)
0

如果您不希望 get() 提供默认值,请使用 try...catch

try
    dict["Z"]
catch error
    if isa(error, KeyError)
        println("sorry, I couldn't find anything")
    end
end

sorry, I couldn't find anything

要更改分配给现有键的值(或将值分配给以前未见过的键)

julia> dict["a"] = 10
10

字典的键必须是唯一的。这个字典中始终只有一个名为 a 的键,因此当您将值分配给已经存在的键时,您不是在创建新的键,而只是修改现有的键。

要查看字典是否包含键,请使用 haskey()

julia> haskey(dict, "Z")
false

要检查键/值对是否存在

julia> in(("b" => 2), dict)
true

要向字典添加新的键和值,请使用以下方法

julia> dict["d"] = 4
4

您可以使用 delete!() 从字典中删除键

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

您会注意到字典似乎没有以任何方式排序 - 至少,键没有特定的顺序。这是由于它们的存储方式,您无法就地对它们进行排序。(但请参阅下面的 排序。)

要获取所有键,请使用 keys() 函数

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5);
julia> keys(dict)
Base.KeySet for a Dict{String,Int64} with 5 entries. Keys:
 "c"
 "e"
 "b"
 "a"
 "d"

结果是一个迭代器,它只有一个工作:逐键迭代字典

julia> collect(keys(dict))
5-element Array{String,1}:
"c"
"e"
"b"
"a"
"d"

julia> [uppercase(key) for key in keys(dict)]
5-element Array{Any,1}:
"C"
"E"
"B"
"A"
"D"

这使用列表推导形式([ 新元素 for 循环变量 in 迭代器 ]),并且每个新元素都被收集到数组中。另一种方法是

julia> map(uppercase, collect(keys(dict)))
5-element Array{String,1}:
"C"
"E"
"B"
"A"
"D"

要检索所有值,请使用 values() 函数

julia> values(dict)
Base.ValueIterator for a Dict{String,Int64} with 5 entries. Values:
 3
 5
 2
 1
 4

如果您想遍历字典并处理每个键/值,您可以利用字典本身是可迭代对象的事实

julia> for kv in dict
   println(kv)
end

"c"=>3
"e"=>5
"b"=>2
"a"=>1
"d"=>4

其中 kv 是一个元组,依次包含每个键/值对。

或者您可以这样做

julia> for k in keys(dict)
          println(k, " ==> ", dict[k])
       end

c ==> 3
e ==> 5
b ==> 2
a ==> 1
d ==> 4

更好的是,您可以使用键/值元组来进一步简化迭代

julia> for (key, value) in dict
           println(key, " ==> ", value)
       end

c ==> 3
e ==> 5
b ==> 2
a ==> 1
d ==> 4

以下是一个示例

for tuple in Dict("1"=>"Hydrogen", "2"=>"Helium", "3"=>"Lithium")
    println("Element $(tuple[1]) is $(tuple[2])")
end

Element 1 is Hydrogen
Element 2 is Helium
Element 3 is Lithium

(注意字符串插值运算符 $。这使您可以在字符串中使用变量的名称,并在打印字符串时获取变量的值。您可以使用 $() 在字符串中包含任何 Julia 表达式。)

对字典进行排序

[编辑 | 编辑源代码]

因为字典不按任何特定顺序存储键,所以您可能想要将字典输出到排序数组中,以按顺序获取项目

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6)
Dict{String,Int64} with 6 entries:
 "f" => 6
 "c" => 3
 "e" => 5
 "b" => 2
 "a" => 1
 "d" => 4
julia> for key in sort(collect(keys(dict)))
   println("$key => $(dict[key])")
end
a => 1
b => 2
c => 3
d => 4
e => 5
f => 6

如果您确实需要始终保持排序的字典,您可以使用 DataStructures.jl 包中的 SortedDict 数据类型(安装后)。

julia> import DataStructures
julia> dict = DataStructures.SortedDict("b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6)
DataStructures.SortedDict{String,Int64,Base.Order.ForwardOrdering} with 5 entries:
 "b" => 2
 "c" => 3
 "d" => 4
 "e" => 5
 "f" => 6
julia> dict["a"] = 1
1
julia> dict
DataStructures.SortedDict{String,Int64,Base.Order.ForwardOrdering} with 6 entries:
 "a" => 1
 "b" => 2
 "c" => 3
 "d" => 4
 "e" => 5
 "f" => 6

Julia 的最新版本会为您排序字典

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6)
Dict{String,Int64} with 6 entries:
  "f" => 6
  "c" => 3
  "e" => 5
  "b" => 2
  "a" => 1
  "d" => 4
   
julia> sort(dict)
OrderedCollections.OrderedDict{String,Int64} with 6 entries:
  "a" => 1
  "b" => 2
  "c" => 3
  "d" => 4
  "e" => 5
  "f" => 6

简单示例:统计词频

[编辑 | 编辑源代码]

字典的一个简单应用是统计文本中每个词出现的次数。每个词都是一个键,键的值是该词在文本中出现的次数。

让我们统计一下福尔摩斯故事中的词语。我已经从优秀的 Project Gutenberg 下载了文本,并将它们存储在一个名为“sherlock-holmes-canon.txt”的文件中。要从 canon 中加载的文本创建单词列表,我们将使用正则表达式拆分文本,并将每个单词转换为小写。(可能还有更快的办法。)

julia> f = open("sherlock-holmes-canon.txt")
julia> wordlist = String[]
julia> for line in eachline(f)
   words = split(line, r"\W")
   map(w -> push!(wordlist, lowercase(w)), words)
end
julia> filter!(!isempty, wordlist)
julia> close(f)

wordlist 现在是一个包含近 700,000 个单词的数组

julia> wordlist[1:20]
20-element Array{String,1}:
"THE"     
"COMPLETE"
"SHERLOCK"
"HOLMES"  
"Arthur"  
"Conan"   
"Doyle"   
"Table"   
"of"      
"contents"
"A"       
"Study"   
"In"      
"Scarlet" 
"The"     
"Sign"    
"of"      
"the"     
"Four"    
"The"    

要存储单词和单词计数,我们将创建一个字典

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

要构建字典,请遍历单词列表,并使用 get() 查找当前计数(如果有)。如果该词已经出现过,则可以增加计数。如果该词以前从未见过,则 get() 的回退第三个参数确保不存在不会导致错误,并且将存储 1。

for word in wordlist
    wordcounts[word]=get(wordcounts, word, 0) + 1
end

现在,您可以在 wordcounts 字典中查找单词,并找出它们出现的次数

julia> wordcounts["watson"]
1040

julia> wordcounts["holmes"]
3057

julia> wordcounts["sherlock"]
415

julia> wordcounts["lestrade"]
244

字典未排序,但您可以对字典使用 collect()keys() 函数来收集键,然后对它们进行排序。在一个循环中,您可以按字母顺序处理字典

for i in sort(collect(keys(wordcounts)))
  println("$i, $(wordcounts[i])")
end
 000, 5
 1, 8
 10, 7
 100, 4
 1000, 9
 104, 1
 109, 1
 10s, 2
 10th, 1
 11, 9
 1100, 1
 117, 2
 117th, 2
 11th, 1
 12, 2
 120, 2
 126b, 3
            
 zamba, 2
 zeal, 5
 zealand, 3
 zealous, 3
 zenith, 1
 zeppelin, 1
 zero, 2
 zest, 3
 zig, 1
 zigzag, 3
 zigzagged, 1
 zinc, 3
 zion, 2
 zoo, 1
 zoology, 2
 zu, 1
 zum, 2
 â, 41
 ã, 4

但您如何找出最常见的词语呢?一种方法是使用 collect() 将字典转换为元组数组,然后通过查看每个元组的最后一个值来对数组进行排序

julia> sort(collect(wordcounts), by = tuple -> last(tuple), rev=true)
19171-element Array{Pair{String,Int64},1}:
("the",36244)     
("and",17593)     
("i",17357)       
("of",16779)      
("to",16041)      
("a",15848)       
("that",11506)   
⋮                 
("enrage",1)      
("smuggled",1)    
("lounges",1)     
("devotes",1)     
("reverberated",1)
("munitions",1)   
("graybeard",1) 

要查看前 20 个单词

julia> sort(collect(wordcounts), by = tuple -> last(tuple), rev=true)[1:20]
20-element Array{Pair{String,Int64},1}:
("the",36244) 
("and",17593) 
("i",17357)   
("of",16779)  
("to",16041)  
("a",15848)   
("that",11506)
("it",11101)  
("in",10766)  
("he",10366)  
("was",9844)  
("you",9688)  
("his",7836)  
("is",6650)   
("had",6057)  
("have",5532) 
("my",5293)   
("with",5256) 
("as",4755)   
("for",4713) 

以类似的方式,您可以使用 filter() 函数来查找例如所有以“k”开头且出现次数少于四次的单词

julia> filter(tuple -> startswith(first(tuple), "k") && last(tuple) < 4, collect(wordcounts))
73-element Array{Pair{String,Int64},1}:
("keg",1)
("klux",2)
("knifing",1)
("keening",1)
("kansas",3)
⋮
("kaiser",1)
("kidnap",2)
("keswick",1)
("kings",2)
("kratides",3)
("ken",2)
("kindliness",2)
("klan",2)
("keepsake",1)
("kindled",2)
("kit",2)
("kicking",1)
("kramm",2)
("knob",1)

更复杂的结构

[编辑 | 编辑源代码]

字典可以保存多种不同类型的值。例如,这里有一个字典,它的键是字符串,而值是点数组(假设 Point 类型已经定义)。例如,这可以用来存储描述字母的图形形状(其中一些有 2 个或更多个循环)

julia> p = Dict{String, Array{Array}}()
Dict{String,Array{Array{T,N},N}}
    
julia> p["a"] = Array[[Point(0,0), Point(1,1)], [Point(34, 23), Point(5,6)]]
2-element Array{Array{T,N},1}:
 [Point(0.0,0.0), Point(1.0,1.0)]
 [Point(34.0,23.0), Point(5.0,6.0)]
   
julia> push!(p["a"], [Point(34.0,23.0), Point(5.0,6.0)])
3-element Array{Array{T,N},1}:
 [Point(0.0,0.0), Point(1.0,1.0)]
 [Point(34.0,23.0), Point(5.0,6.0)]
 [Point(34.0,23.0), Point(5.0,6.0)]

或者创建一个包含一些已知值的字典

julia> d = Dict("shape1" => Array [ [ Point(0,0), Point(-20,57)], [Point(34, -23), Point(-10,12) ] ])
Dict{String,Array{Array{T,N},1}} with 1 entry:
 "shape1" => Array [ [ Point(0.0,0.0), Point(-20.0,57.0)], [Point(34.0,-23.0), Point(-10.0,12.0) ] ]

向第一个数组添加另一个数组

julia> push!(d["shape1"], [Point(-124.0, 37.0), Point(25.0,32.0)])
3-element Array{Array{T,N},1}:
 [Point(0.0,0.0), Point(-20.0,57.0)]
 [Point(34.0,-23.0), Point(-10.0,12.0)]
 [Point(-124.0,37.0), Point(25.0,32.0)]

集合是元素的集合,就像数组或字典一样,没有重复的元素。

集合与其他类型集合的两个重要区别是:在集合中,你只能拥有每个元素的一个,并且在集合中,元素的顺序并不重要(而数组可以包含一个元素的多个副本,并且它们的顺序会被记住)。

你可以使用 Set 构造函数创建空集合

julia> colors = Set()
Set{Any}({})

与 Julia 中的其他地方一样,你可以指定类型

julia> primes = Set{Int64}()
Set(Int64)[]

你可以在一步内创建并填充集合

julia> colors = Set{String}(["red","green","blue","yellow"])
Set(String["yellow","blue","green","red"])

或者你可以让 Julia "猜测类型"

julia> colors = Set(["red","green","blue","yellow"])
Set{String}({"yellow","blue","green","red"})

许多用于数组的函数也适用于集合。例如,向集合添加元素有点像向数组添加元素。你可以使用 push!()

julia> push!(colors, "black") 
Set{String}({"yellow","blue","green","black","red"})

但你不能使用 pushfirst!(),因为它只适用于具有 "第一个" 概念的事物,比如数组。

如果你尝试向集合中添加已经存在的元素会发生什么?什么也不会发生。你不会添加副本,因为它是一个集合,而不是一个数组,并且集合不存储重复的元素。

要查看集合中是否包含某个元素,你可以使用 in()

julia> in("green", colors)
true

你可以对集合执行一些标准操作,即找到它们的 **并集**、**交集** 和 **差集**,分别使用函数 union()intersect()setdiff()

julia> rainbow = Set(["red","orange","yellow","green","blue","indigo","violet"])
Set(String["indigo","yellow","orange","blue","violet","green","red"])

两个集合的并集是包含所有属于这两个集合的元素的集合。结果是另一个集合,因此你不能有两个 "yellow",即使每个集合都包含一个 "yellow"

julia> union(colors, rainbow)
Set(String["indigo","yellow","orange","blue","violet","green","black","red"])

两个集合的交集是包含属于这两个集合的每个元素的集合

julia> intersect(colors, rainbow)
Set(String["yellow","blue","green","red"])

两个集合的差集是包含第一个集合中存在的元素,但不在第二个集合中的元素的集合。这次,你提供集合的顺序很重要。setdiff() 函数找到第一个集合 colors 中存在但不在第二个集合 rainbow 中的元素

julia> setdiff(colors, rainbow)
Set(String["black"])

其他函数

[编辑 | 编辑源代码]

在数组和集合上运行的函数有时也适用于字典和其他集合。例如,一些集合操作可以应用于字典,而不仅仅是集合和数组

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

julia> union(d1, d2)
4-element Array{Pair{Int64,String},1}:
 2=>"b"
 1=>"a"
 4=>"d"
 3=>"c"

julia> intersect(d1, d2)
1-element Array{Pair{Int64,String},1}:
 2=>"b"
 
julia> setdiff(d1, d2)
1-element Array{Pair{Int64,String},1}:
 1=>"a"

请注意,结果作为 Pair 数组返回,而不是作为字典返回。

我们已经看到过用于数组的 filter()map()collect() 等函数也适用于字典

julia> filter((k, v) -> k == 1, d1)
Dict{Int64,String} with 1 entry:
  1 => "a"

有一个 merge() 函数可以合并两个字典

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

findmin() 函数可以在字典中找到最小值,并返回该值及其键。

julia> d1 = Dict(:a => 1, :b => 2, :c => 0)
Dict{Symbol,Int64} with 3 entries:
 :a => 1
 :b => 2
 :c => 0

julia> findmin(d1)
(0, :c)
华夏公益教科书