Elixir 簡明筆記(十四) --- 字典容器

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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,602評論 19 139
  • //Clojure入門教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語閱讀 4,053評論 0 7
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,734評論 18 399
  • 風扇呼呼的回聲,魚缸水流的響聲,廚房高壓鍋飄散著濃郁的香味,時光剪在炎熱的夏季,衣服貼肉的躁熱,汗流鼻尖的黏濕。 ...
    書眠閱讀 192評論 0 0
  • 情緒不對時,果然不適合看愛情片。 上段時間,有兩天不在狀態(tài),在家看了兩部韓國純愛電影,感動得一敗涂地,幸好及時看了...
    阿銀老師閱讀 1,808評論 0 4

友情鏈接更多精彩內(nèi)容