Clojure 學(xué)習(xí)筆記 :7 map --- 可能是最有用的數(shù)據(jù)結(jié)構(gòu)

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ù)的用法:

  1. 第一個(gè)參數(shù)是要訪問的 map
  2. 第二個(gè)參數(shù)是要訪問的 map 中的 key
  3. 第三個(gè)參數(shù)可選缕坎,作用是設(shè)置一個(gè)默認(rèn)值,如果 map 中訪問的 key 不存在从橘,則返回這個(gè)參數(shù)的值
  4. 正常情況下返回 key 所對(duì)應(yīng)的 value
  5. 如果第三個(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ù)的使用方法是這樣的:

  1. 第一個(gè)參數(shù)是要訪問的 map
  2. 第二個(gè)參數(shù)可選(和 get 函數(shù)的第三個(gè)參數(shù)作用一致)
  3. 在 map 中尋找和 key 函數(shù)本身相同的 key(其它特性與 get 函數(shù)相似)

除了讀取 map 中的內(nèi)容,我們還可以使用 assocdissoc 來增加和刪除 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)行說明。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末闪萄,一起剝皮案震驚了整個(gè)濱河市梧却,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌败去,老刑警劉巖放航,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異圆裕,居然都是意外死亡广鳍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門吓妆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赊时,“玉大人,你說我怎么就攤上這事行拢∽婷耄” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長竭缝。 經(jīng)常有香客問我房维,道長,這世上最難降的妖魔是什么抬纸? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任咙俩,我火速辦了婚禮,結(jié)果婚禮上湿故,老公的妹妹穿的比我還像新娘阿趁。我一直安慰自己,他們只是感情好坛猪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布脖阵。 她就那樣靜靜地躺著,像睡著了一般砚哆。 火紅的嫁衣襯著肌膚如雪独撇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天躁锁,我揣著相機(jī)與錄音纷铣,去河邊找鬼。 笑死战转,一個(gè)胖子當(dāng)著我的面吹牛搜立,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播槐秧,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼啄踊,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了刁标?” 一聲冷哼從身側(cè)響起颠通,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膀懈,沒想到半個(gè)月后顿锰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡启搂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年硼控,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胳赌。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牢撼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疑苫,到底是詐尸還是另有隱情熏版,我是刑警寧澤纷责,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站纳决,受9級(jí)特大地震影響碰逸,放射性物質(zhì)發(fā)生泄漏乡小。R本人自食惡果不足惜阔加,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望满钟。 院中可真熱鬧胜榔,春花似錦、人聲如沸湃番。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吠撮。三九已至尊惰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泥兰,已是汗流浹背弄屡。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鞋诗,地道東北人膀捷。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像削彬,于是被迫代替她去往敵國和親全庸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容