跳轉到內容

介紹 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)
華夏公益教科書