Elixir提供了豐富的數(shù)據(jù)類型(實際上只是基本類型的擴展)。前面介紹了元組和列表。他們的特點就是線性結(jié)構(gòu),通常稱之為序列。此外,elixir還有一些哈希類型的結(jié)構(gòu),這些可以稱之為容器集合(collection)結(jié)構(gòu)。下面就來介紹elixir中的一些哈希容器。
圖 map
哈希結(jié)構(gòu)是通過一些鍵值對組合的字典。每一種編程語言中都提供了哈希結(jié)構(gòu)。elixir中更是提供了多種哈希結(jié)構(gòu)。其中Map就是比較常用的一種。圖是由一個鍵(key)和值(value)為基本存儲單元的結(jié)構(gòu)。key和value都可以是任意類型的數(shù)據(jù)。Map是在Erlang/OTP 17.0之后引入的數(shù)據(jù)結(jié)構(gòu)。map通常只用來表示元素比較少的情況,當有大量元素的時候,elixir推薦使用HashDict這個結(jié)構(gòu)(稍后介紹)。
map 的定義
map的定義也比較簡單,使用一個%跟隨一堆花括號{}。鍵和值之間使用 =>(通常其他hash結(jié)構(gòu)使用:, 例如python和js)。例如:
iex(1)> bob = %{:name => "Bob", :age => 25, :work_at => "Initech"}
%{age: 25, name: "Bob", work_at: "Initech"}
定義了一個簡單地map,其中鍵都是Atom類型,值有字符串和數(shù)字。針對這種map,可以使用下面的語法糖簡寫:
iex(1)> bob = %{:name => "Bob", :age => 25, :work_at => "Initech"}
%{age: 25, name: "Bob", work_at: "Initech"}
iex(2)> bob2 = %{name: "Bob", age: 25, work_at: "Initech"}
%{age: 25, name: "Bob", work_at: "Initech"}
iex(3)> bob === bob2
true
#### map 的讀取
讀取一個map鍵的值,使用中括號[]加上鍵名的atom即可。也可以使用 .操作符,前者訪問不存在的鍵的時候,將會返回nil,后者則會拋出語法錯誤:
iex(5)> bob[:work_at]
"Initech"
iex(6)> bob[:non_existent_field]
nil
iex(7)> bob.work_at
"Initech"
iex(8)> bob.:non_existent_field
** (SyntaxError) iex:8: syntax error before: non_existent_field
map 的修改
map的修改使用 | ,因為elixir的數(shù)據(jù)都是不可變的,因此修改將會返回一個新的map。也可以同時修改多個鍵的值。但是只能修改已經(jīng)存在的key,否則會報錯。之所以這樣限制,其目的是為了更有效的提高map更新的效率。因為map結(jié)構(gòu)不會變,那么修改前后各個“版本”的map還將會被引用,可是使用更少的內(nèi)存,操作也就更加迅速:
iex(8)> next_years_bob = %{bob | age: 26}
%{age: 26, name: "Bob", work_at: "Initech"}
iex(9)> bob
%{age: 25, name: "Bob", work_at: "Initech"}
iex(10)> %{bob | age: 26, work_at: "Initrode"}
%{age: 26, name: "Bob", work_at: "Initrode"}
iex(11)> %{bob | non_existent_feild: 'new'}
** (ArgumentError) argument error
(stdlib) :maps.update(:non_existent_feild, 'new', %{age: 25, name: "Bob", work_at: "Initech"})
(stdlib) erl_eval.erl:255: anonymous fn/2 in :erl_eval.expr/5
(stdlib) lists.erl:1261: :lists.foldl/3
除了使用 | 更新map之外,還可以使用模塊的方法,即使用Map模塊和Dict模塊來更新map。并且使用模塊的方式可以增加新的key和值, 但是不能同時更新多個值:
iex(13)> Map.put(bob, :salary, 5000)
%{age: 25, name: "Bob", salary: 5000, work_at: "Initech"}
iex(14)> Map.put(bob, :age, 26)
%{age: 26, name: "Bob", work_at: "Initech"}
iex(15)> Map.put(bob, :age, 26, :work_at, "Home")
** (UndefinedFunctionError) undefined function: Map.put/5
(elixir) Map.put(%{age: 25, name: "Bob", work_at: "Initech"}, :age, 26, :work_at, "Home")
iex(15)> Dict.put(bob, :salary, 5000)
%{age: 25, name: "Bob", salary: 5000, work_at: "Initech"}
iex(16)> Dict.put(bob, :age, 26)
%{age: 26, name: "Bob", work_at: "Initech"}
iex(17)> Dict.put(bob, :salary, 5000, :work_at, "Home")
** (UndefinedFunctionError) undefined function: Dict.put/5
(elixir) Dict.put(%{age: 25, name: "Bob", work_at: "Initech"}, :salary, 5000, :work_at, "Home")
通常情況下,使用Map模塊要比Dict模塊的操作速度更快,不過map用于存儲少量的元素。因此,其自身提供的方法可以應付很多應用場景。
map的模式匹配
毫無疑問,elixir中模式匹配無處不在,map當然也可以進行模式匹配。與列表,元組不一樣,map的模式匹配,不需要把所有的key和value都寫入模式里, 匹配不存在的key會失敗,因而會報錯:
iex(17)> %{name: real_name} = bob
%{age: 25, name: "Bob", work_at: "Initech"}
iex(18)> real_name
"Bob"
iex(19)> %{name: real_name, age: real_age} = bob
%{age: 25, name: "Bob", work_at: "Initech"}
iex(20)> %{name: real_name, age: real_age, salary: real_salary} = bob
** (MatchError) no match of right hand side value: %{age: 25, name: "Bob", work_at: "Initech"}
關(guān)鍵字列表 Keyword List
定義
map是標準的哈稀結(jié)構(gòu),這樣的鍵值字典,elixir還有好幾種。Keyword算是一種奇怪的哈稀,有其行而無其實。通常定義一個列表使用中括號。定義一個兩個元素的元組。如果元祖的第一個元素是一個atom,那么就可以寫成下面類似哈稀的結(jié)構(gòu):
iex(20)> bob = [{:name, "Bob"}, {:age, 25}, {:work_at, "Intiech"}]
[name: "Bob", age: 25, work_at: "Intiech"] # 返回值就是keyword list的字面方式
iex(21)> bob2 = [name: "Bob", age: 25, work_at: Intiech]
[name: "Bob", age: 25, work_at: "Intiech"]
iex(22)> bob === bob2
true
iex(25)> tom = [{25, :age}, {:name, "Tom"}]
[{25, :age}, {:name, "Tom"}]
獲取keyword的值
keyword list的讀取可以使用 Keyword 模塊。通常keyword也是比較小的key-value數(shù)據(jù)結(jié)構(gòu):
iex(28)> Keyword.get(days, :monday)
1
iex(29)> Keyword.get(days, :noday)
nil
iex(31)> days[:monday]
1
iex(32)> days[:noday]
nil
iex(33)> days.monday
** (ArgumentError) argument error
:erlang.apply([monday: 1, tuesday: 2, wednesday: 3], :monday, [])
keyword 的讀取操作和map及其類似??墒莐eyword畢竟還是列表,讀取某個鍵的值所消耗的時間復雜度還是O(n)。而map則是O(1)。keyword的應用場景呢?通常用于函數(shù)的參數(shù)來傳遞,例如Float.to_string的使用:
iex(35)> Float.to_string(1/3)
"3.33333333333333314830e-01"
iex(36)> Float.to_string(1/3, [decimals: 2])
"0.33"
實際使用中,elixir往往可以讓你省略中苦熬和的書寫:
iex(37)> Float.to_string(1/3, decimals: 2, compact: true)
"0.33"
iex(38)> Float.to_string(1/3, [decimals: 2, compact: true])
"0.33"
keyword 和 map如此相似,你肯定有疑問到底如何取舍。Elixir中很多函數(shù)的可選參數(shù)都是keyword,主要原因卻有點滑稽。因為Map對于Erlang是比較新的數(shù)據(jù)結(jié)構(gòu),在此之前,map所能執(zhí)行的功能基本都是使用keyword。當然,即使增加了map,兩者也不是完全可以替代。比如,keyword允許多個相同的key存在,并且每個key-value是有順序的。因此實際情況中,他們兩者的使用更多的取決于當時你的應用場景。
HashDict
map適合元素比較少的key-value結(jié)構(gòu)。當遭遇大量元素的適合,HashDict將會是很好的幫手。
創(chuàng)建
HashDict是一個模塊,用于創(chuàng)建 HashDict 的“實例”結(jié)構(gòu)。使用模塊的new方法可以創(chuàng)建一個空的HashDict結(jié)構(gòu)。
iex(39)> HashDict.new
#HashDict<[]>
為了創(chuàng)建多個元素的HashDict,需要借助Enum模塊的into方法:
iex(47)> days = [monday: 1, tuesday: 2, wednesday: 3] |>
...(47)> Enum.into(HashDict.new)
#HashDict<[monday: 1, tuesday: 2, wednesday: 3]>
Enum.into 函數(shù)可以將任何可以枚舉(enumerable)轉(zhuǎn)換成可以容器化(collectable)的結(jié)構(gòu)。關(guān)于可枚舉和可容器化將會在協(xié)議(protcol)中討論。此時只需要制知道如何借助其創(chuàng)建一個HashDict。
讀寫HashDict
獲取key的值比較簡單,使用模塊的get方法接口,當key不存在,會返回nil
iex(48)> HashDict.get(days, :monday)
1
iex(49)> HashDict.get(days, :nodays)
nil
使用 put 方法進行修改和增加新的key-value
iex(51)> HashDict.put(days, :thursday, 4)
#HashDict<[monday: 1, tuesday: 2, wednesday: 3, thursday: 4]>
iex(52)> HashDict.put(days, :tuesday, "two")
#HashDict<[monday: 1, tuesday: "two", wednesday: 3]>
由此可見,類似的容器字典結(jié)構(gòu),都可以使用 Map,Keyword,HashDict的get方法獲取不存在的key的值,使用put方法更新和增加key-value。
HashDict實現(xiàn)了枚舉協(xié)議,也就是可枚舉,當然可以使用Enum的一些方法,比如枚舉key-value
iex(54)> days = HashDict.put(days, :thursday, 4)
#HashDict<[monday: 1, tuesday: 2, wednesday: 3, thursday: 4]>
iex(55)> Enum.each(
...(55)> days,
...(55)> fn(key_value) ->
...(55)> key = elem(key_value, 0)
...(55)> value = elem(key_value, 1)
...(55)> IO.puts "#{key} => #{value}"
...(55)> end )
monday => 1
tuesday => 2
wednesday => 3
thursday => 4
:ok
HashDict和map的差別還是比較好區(qū)分,HashDict對于大量的元素集合,其性能會比map好,而map卻提供了很多操作上語法糖。
無論Map,Keyword,HashDict都是實現(xiàn)字典式的操作??梢园l(fā)現(xiàn),他們的模塊方法,通常配合這Enum模塊進行使用。實際上,Enum模塊是Elixir中重要模塊,它提供了很多對數(shù)據(jù)結(jié)構(gòu)操作的高級封裝。有些函數(shù)直接省去了需要遞歸實現(xiàn)的循環(huán)。
一般的編程語言的介紹,通常流程就是介紹其基本數(shù)據(jù)結(jié)構(gòu),然后就介紹一下控制結(jié)構(gòu)。至此,elixir基本數(shù)據(jù)類型我們都了解了一遍,接下來將會討論其控制結(jié)構(gòu)。