介绍 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)