介绍 Julia/字典和集合
到目前为止介绍的许多函数都已在数组(和元组)上运行。但是数组只是集合类型之一。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)