Clojure
零基礎(chǔ)
學(xué)習(xí)筆記
map
map 是一種映射關(guān)系
map 長啥樣
這次介紹的 map 不是高階函數(shù) map
星瘾,而是我們?cè)诘诙恼?a href="http://www.reibang.com/p/01f30049c1e2" target="_blank">《你好,集合》里面曾經(jīng)提到名字的一種 Clojure 提供的數(shù)據(jù)結(jié)構(gòu)尺棋。高階函數(shù) map
像是一個(gè)動(dòng)詞,表示一種函數(shù)和一系列參數(shù)之間的“映射”動(dòng)作绵跷。而數(shù)據(jù)結(jié)構(gòu) map 是一個(gè)名詞陡鹃,它表示“關(guān)鍵字”和“值”之間的對(duì)應(yīng)關(guān)系。也就是業(yè)界通常簡(jiǎn)稱的“鍵值對(duì)”抖坪、key-value萍鲸。
那到底長啥樣呢?我們來寫幾個(gè) map
{"鍵" "值"}
{"關(guān)鍵字1" "值1" "關(guān)鍵字2" "值2"} ;不推薦這種使用空格來間隔的寫法
{:name "blindingdark", :age "不告訴你"} ;如果寫在一行擦俐,每一組鍵值對(duì)之間用逗號(hào)間隔
{:name "FFF團(tuán)火法師"
:level 32
:coins 128
:items ["生銹的普通匕首" "火把" "汽油"]} ;不過我們更建議在每組鍵值對(duì)之間換行
接觸過 JSON 的同學(xué)應(yīng)該非常熟悉這種形式脊阴。
使用花括號(hào) {}
來包圍一組鍵值對(duì),鍵和值可以是任意表達(dá)式(如字符串蚯瞧,數(shù)字嘿期,list,vector)埋合。每一對(duì)鍵值對(duì)中备徐,鍵和值用空格隔開,不同的鍵值對(duì)之間使用逗號(hào)或者空格或者換行隔開甚颂。
上面的例子中也對(duì)間隔符號(hào)進(jìn)行了說明蜜猾,雖然 Clojure 允許多種個(gè)性化的風(fēng)格,但無論哪種風(fēng)格振诬,總是要保持良好的可讀性蹭睡。
不過通常我們使用一種特別的形式來表示“鍵”,即使用 冒號(hào)+關(guān)鍵字名
的形式來表示赶么。你馬上就會(huì)在接下來的內(nèi)容中看到肩豁,如何使用這種形式更方便的訪問 map 中的元素。
map 中的 key 必須保證唯一性!
如果你在一個(gè) map 里使用了重名的 key清钥,那么就會(huì)報(bào)錯(cuò)琼锋。
=> {"鍵" "值1"
"鍵" "值2"}
IllegalArgumentException Duplicate key: 鍵 clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
操作 map 元素
首先我們假設(shè)有一名玩家的信息是這樣的
(def player-info {:name "FFF團(tuán)火法師"
:level 32
:coins 128
:items ["生銹的普通匕首" "火把" "汽油"]})
那么我們?cè)趺慈サ玫竭@個(gè)用戶的信息呢?
我們可以使用 get
函數(shù)來得到某個(gè) key 的 value祟昭。
小貼士:使用英文可以有效提高逼格
=> (get player-info :items)
["生銹的普通匕首" "火把" "汽油"]
=> (get player-info :level)
32
=> (get player-info :sex)
nil
=> (get player-info :sex "沒有找到這個(gè)屬性")
"沒有找到這個(gè)屬性"
很容易就可以總結(jié)出 get
函數(shù)的用法:
- 第一個(gè)參數(shù)是要訪問的 map
- 第二個(gè)參數(shù)是要訪問的 map 中的 key
- 第三個(gè)參數(shù)可選缕坎,作用是設(shè)置一個(gè)默認(rèn)值,如果 map 中訪問的 key 不存在从橘,則返回這個(gè)參數(shù)的值
- 正常情況下返回 key 所對(duì)應(yīng)的 value
- 如果第三個(gè)參數(shù)未填寫念赶,那么一旦 map 中沒有所訪問的 key,則返回
nil
get 函數(shù)不僅能訪問 map恰力,有關(guān)它的其他用法請(qǐng)查詢相關(guān) api叉谜。
如果你使用了 冒號(hào)+關(guān)鍵字名
這種形式來表示一個(gè) key,那么我們就可以更加簡(jiǎn)便的訪問 map 中的 value 了踩萎。
=> (:name player-info)
"FFF團(tuán)火法師"
=> (:sex player-info)
nil
=> (:sex player-info "not found")
"not found"
我們發(fā)現(xiàn)停局, 冒號(hào)+關(guān)鍵字名
這種形式的表示,竟然能放在括號(hào)的第一個(gè)位置香府,這說明它也是一個(gè)函數(shù)董栽。所以這種奇特的函數(shù)的使用方法是這樣的:
- 第一個(gè)參數(shù)是要訪問的 map
- 第二個(gè)參數(shù)可選(和
get
函數(shù)的第三個(gè)參數(shù)作用一致) - 在 map 中尋找和 key 函數(shù)本身相同的 key(其它特性與
get
函數(shù)相似)
除了讀取 map 中的內(nèi)容,我們還可以使用 assoc
和 dissoc
來增加和刪除 map 中的元素企孩。
比如我們的火法師成功的燒燒燒了一對(duì)情侶锭碳,獲得 100 金幣,獲得稱號(hào):大師級(jí)火焰掌控
首先,我們使用 assoc
函數(shù)向 player-info 添加一個(gè)鍵值對(duì) 稱號(hào)-稱號(hào)名
=> (assoc player-info :achieve "大師級(jí)火焰掌控")
{:name "FFF團(tuán)火法師", :level 32, :coins 128, :items ["生銹的普通匕首" "火把" "汽油"], :achieve "大師級(jí)火焰掌控"}
還記得我們之前說過的使用 def
聲明的值是不可變的 么?所以此時(shí)我們?cè)L問 player-info 會(huì)發(fā)現(xiàn)它并沒有改變悔雹。
=> player-info
{:name "FFF團(tuán)火法師", :level 32, :coins 128, :items ["生銹的普通匕首" "火把" "汽油"]}
所以我們得重新聲明:
=> (def player-info (assoc player-info :achieve "大師級(jí)火焰掌控"))
#'user/player-info
如果 assoc
函數(shù)所添加的 key 已經(jīng)存在,就會(huì)使用新值來覆蓋歧沪。所以我們?cè)诖耸褂盟鼇砀淖兘饚诺臄?shù)量。
=> (assoc player-info :coins (+ 100 (:coins player-info)))
{:name "FFF團(tuán)火法師", :level 32, :coins 228, :items ["生銹的普通匕首" "火把" "汽油"], :achieve "大師級(jí)火焰掌控"}
當(dāng)然莲组,它還是沒有改變 player-info 的值诊胞。
我們還可以一次性對(duì) map 進(jìn)行多個(gè)值的修改,只需要把需要添加的鍵值對(duì)依次寫上:
=> (assoc player-info
:coins (+ 100 (:coins player-info))
:achieve "大師級(jí)火焰掌控")
{:name "FFF團(tuán)火法師", :level 32, :coins 228, :items ["生銹的普通匕首" "火把" "汽油"], :achieve "大師級(jí)火焰掌控"}
如果要?jiǎng)h除某個(gè)鍵值對(duì)锹杈,我們可以使用 dissoc
函數(shù)撵孤。比如我們刪除稱號(hào):
=> (dissoc player-info :achieve)
{:name "FFF團(tuán)火法師", :level 32, :coins 228, :items ["生銹的普通匕首" "火把" "汽油"]}
如果我們想得到所有的 key 或者得到所有的 value,可以用 keys
函數(shù)和 vals
函數(shù):
=> (keys player-info)
(:name :level :coins :items)
=> (vals player-info)
("FFF團(tuán)火法師" 32 128 ["生銹的普通匕首" "火把" "汽油"])
map 嵌套
map 中可以嵌套 map(或者嵌套任何你想要的表達(dá)式)嬉橙,這是非常自然的做法早直,你可以一層一層的來組裝你的數(shù)據(jù),以便更好的描述你所需要的內(nèi)容市框。
比如我們可以給我們的 player-info 添加一條信息,來表示已裝備的護(hù)甲:
=> (assoc player-info :armor {:head "巫師帽子", :body "黑色法袍"})
{:name "FFF團(tuán)火法師",
:level 32,
:coins 228,
:items ["生銹的普通匕首" "火把" "汽油"],
:achieve "大師級(jí)火焰掌控",
:armor {:head "巫師帽子", :body "黑色法袍"}}
訪問它的方式大家就開動(dòng)腦筋吧糕韧。
map 解構(gòu)
還記得之前講到的順序解構(gòu)么枫振?map 的解構(gòu)也差不多:
=> (let [{n :name,coins :coins} player-info]
(println n)
(println coins))
FFF團(tuán)火法師
228
nil
只不過要注意喻圃,鍵值對(duì)解構(gòu)要把 key 寫在后面,而給 key 的 value 取的新名字寫在前面粪滤。通常我們把 value 的新名字取的和 key 一樣斧拍,當(dāng)然也可以不一樣。
再來看看 defn
函數(shù)中的解構(gòu)樣式(點(diǎn)這里可以復(fù)習(xí)如何解構(gòu)參數(shù)列表)
讓我們寫一個(gè)函數(shù)杖小,來顯示我們的火法師的姓名和等級(jí):
=> (defn show-level-and-name
[{name :name,coins :coins}]
(println "昵稱:" name)
(println "金幣:" coins))
#'user/show-level-and-name
=> (show-level-and-name player-info)
昵稱: FFF團(tuán)火法師
金幣: 228
nil
同樣我們可以使用嵌套的 map 解構(gòu):
=> (defn show-armors
[{{head :head,body :body} :armor}]
(println "頭部:" head)
(println "身體:" body))
#'user/show-armors
=> (show-armors player-info)
頭部: 巫師帽子
身體: 黑色法袍
nil
我們甚至可以把順序解構(gòu)和 map 解構(gòu)結(jié)合起來肆汹,顯示我們道具欄中的物品:
=> (let [{name :name,coins :coins,[i1 i2 i3] :items} player-info]
(println name)
(println coins)
(println i1 i2 i3))
FFF團(tuán)火法師
228
生銹的普通匕首 火把 汽油
nil
上面我們說了,解構(gòu)的時(shí)候我們習(xí)慣把解構(gòu)之后的名字取成和 key 一樣予权,這樣一來昂勉,我們就要把一個(gè)名字寫兩遍,當(dāng)元素增多的時(shí)候扫腺,重復(fù)勞動(dòng)的負(fù)擔(dān)就無法忍受了岗照。
如果你的 key 都使用 冒號(hào)+名字
的格式,你就可以使用 Clojure 提供的另一種方法:
=> (let [{:keys [coins name]} player-info]
(println "昵稱:" name)
(println "金幣:" coins))
昵稱: FFF團(tuán)火法師
金幣: 128
nil
對(duì)比以前 [{name :name,coins :coins} player-info]
笆环,新形式使用 :keys
放在了 map 解構(gòu)頭部攒至,然后跟上一個(gè) vector ,vector 里面的名字要和 key 的名字一致躁劣,但順序無要求迫吐。
還有許多解構(gòu)的特殊形式,如解構(gòu)剩余內(nèi)容账忘,解構(gòu)的默認(rèn)值志膀,在今后的學(xué)習(xí)中在進(jìn)行說明。