Clojure 學習筆記 :7 map --- 可能是最有用的數(shù)據(jù)結構

Clojure 零基礎 學習筆記 map


map 是一種映射關系

map 長啥樣

這次介紹的 map 不是高階函數(shù) map ,而是我們在第二篇文章《你好,集合》里面曾經(jīng)提到名字的一種 Clojure 提供的數(shù)據(jù)結構。高階函數(shù) map 像是一個動詞,表示一種函數(shù)和一系列參數(shù)之間的“映射”動作。而數(shù)據(jù)結構 map 是一個名詞,它表示“關鍵字”和“值”之間的對應關系。也就是業(yè)界通常簡稱的“鍵值對”、key-value。

那到底長啥樣呢?我們來寫幾個 map

{"鍵" "值"}
{"關鍵字1" "值1" "關鍵字2" "值2"} ;不推薦這種使用空格來間隔的寫法
{:name "blindingdark", :age "不告訴你"} ;如果寫在一行,每一組鍵值對之間用逗號間隔
{:name "FFF團火法師"
 :level 32
 :coins 128
 :items ["生銹的普通匕首" "火把" "汽油"]} ;不過我們更建議在每組鍵值對之間換行

接觸過 JSON 的同學應該非常熟悉這種形式。
使用花括號 {} 來包圍一組鍵值對,鍵和值可以是任意表達式(如字符串,數(shù)字,list,vector)。每一對鍵值對中,鍵和值用空格隔開,不同的鍵值對之間使用逗號或者空格或者換行隔開。

上面的例子中也對間隔符號進行了說明,雖然 Clojure 允許多種個性化的風格,但無論哪種風格,總是要保持良好的可讀性。

不過通常我們使用一種特別的形式來表示“鍵”,即使用 冒號+關鍵字名 的形式來表示。你馬上就會在接下來的內(nèi)容中看到,如何使用這種形式更方便的訪問 map 中的元素。

map 中的 key 必須保證唯一性!
如果你在一個 map 里使用了重名的 key,那么就會報錯。

=> {"鍵" "值1"
    "鍵" "值2"}
IllegalArgumentException Duplicate key: 鍵  clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)

操作 map 元素

首先我們假設有一名玩家的信息是這樣的

 (def player-info {:name "FFF團火法師"
                    :level 32
                    :coins 128
                    :items ["生銹的普通匕首" "火把" "汽油"]})

那么我們怎么去得到這個用戶的信息呢?

我們可以使用 get 函數(shù)來得到某個 key 的 value。

小貼士:使用英文可以有效提高逼格

=> (get player-info :items)
["生銹的普通匕首" "火把" "汽油"]

=> (get player-info :level)
32

=> (get player-info :sex)
nil

=> (get player-info :sex "沒有找到這個屬性")
"沒有找到這個屬性"

很容易就可以總結出 get 函數(shù)的用法:

  1. 第一個參數(shù)是要訪問的 map
  2. 第二個參數(shù)是要訪問的 map 中的 key
  3. 第三個參數(shù)可選,作用是設置一個默認值,如果 map 中訪問的 key 不存在,則返回這個參數(shù)的值
  4. 正常情況下返回 key 所對應的 value
  5. 如果第三個參數(shù)未填寫,那么一旦 map 中沒有所訪問的 key,則返回 nil

get 函數(shù)不僅能訪問 map,有關它的其他用法請查詢相關 api。

如果你使用了 冒號+關鍵字名 這種形式來表示一個 key,那么我們就可以更加簡便的訪問 map 中的 value 了。

=> (:name player-info)
"FFF團火法師"

=> (:sex player-info)
nil

=> (:sex player-info "not found")
"not found"

我們發(fā)現(xiàn), 冒號+關鍵字名 這種形式的表示,竟然能放在括號的第一個位置,這說明它也是一個函數(shù)。所以這種奇特的函數(shù)的使用方法是這樣的:

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

除了讀取 map 中的內(nèi)容,我們還可以使用 assocdissoc 來增加和刪除 map 中的元素。

比如我們的火法師成功的燒燒燒了一對情侶,獲得 100 金幣,獲得稱號:大師級火焰掌控

首先,我們使用 assoc 函數(shù)向 player-info 添加一個鍵值對 稱號-稱號名

=> (assoc player-info :achieve "大師級火焰掌控")
{:name "FFF團火法師", :level 32, :coins 128, :items ["生銹的普通匕首" "火把" "汽油"], :achieve "大師級火焰掌控"}

還記得我們之前說過的使用 def 聲明的值是不可變的 么?所以此時我們訪問 player-info 會發(fā)現(xiàn)它并沒有改變。

=> player-info
{:name "FFF團火法師", :level 32, :coins 128, :items ["生銹的普通匕首" "火把" "汽油"]}

所以我們得重新聲明:

=> (def player-info (assoc player-info :achieve "大師級火焰掌控"))
#'user/player-info

如果 assoc 函數(shù)所添加的 key 已經(jīng)存在,就會使用新值來覆蓋。所以我們在此使用它來改變金幣的數(shù)量。

=> (assoc player-info :coins (+ 100 (:coins player-info)))
{:name "FFF團火法師", :level 32, :coins 228, :items ["生銹的普通匕首" "火把" "汽油"], :achieve "大師級火焰掌控"}

當然,它還是沒有改變 player-info 的值。

我們還可以一次性對 map 進行多個值的修改,只需要把需要添加的鍵值對依次寫上:

=> (assoc player-info
  :coins (+ 100 (:coins player-info))
  :achieve "大師級火焰掌控")
{:name "FFF團火法師", :level 32, :coins 228, :items ["生銹的普通匕首" "火把" "汽油"], :achieve "大師級火焰掌控"}

如果要刪除某個鍵值對,我們可以使用 dissoc 函數(shù)。比如我們刪除稱號:

=> (dissoc player-info :achieve)
{:name "FFF團火法師", :level 32, :coins 228, :items ["生銹的普通匕首" "火把" "汽油"]}

如果我們想得到所有的 key 或者得到所有的 value,可以用 keys 函數(shù)和 vals 函數(shù):

=> (keys player-info)
(:name :level :coins :items)

=> (vals player-info)
("FFF團火法師" 32 128 ["生銹的普通匕首" "火把" "汽油"])

map 嵌套

map 中可以嵌套 map(或者嵌套任何你想要的表達式),這是非常自然的做法,你可以一層一層的來組裝你的數(shù)據(jù),以便更好的描述你所需要的內(nèi)容。

比如我們可以給我們的 player-info 添加一條信息,來表示已裝備的護甲:

=> (assoc player-info :armor {:head "巫師帽子", :body "黑色法袍"})
{:name "FFF團火法師",
 :level 32,
 :coins 228,
 :items ["生銹的普通匕首" "火把" "汽油"],
 :achieve "大師級火焰掌控",
 :armor {:head "巫師帽子", :body "黑色法袍"}}

訪問它的方式大家就開動腦筋吧。


map 解構

還記得之前講到的順序解構么?map 的解構也差不多:

=> (let [{n :name,coins :coins} player-info]
  (println n)
  (println coins))
FFF團火法師
228
nil

只不過要注意,鍵值對解構要把 key 寫在后面,而給 key 的 value 取的新名字寫在前面。通常我們把 value 的新名字取的和 key 一樣,當然也可以不一樣。

再來看看 defn 函數(shù)中的解構樣式(點這里可以復習如何解構參數(shù)列表

讓我們寫一個函數(shù),來顯示我們的火法師的姓名和等級:

=> (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團火法師
金幣: 228
nil

同樣我們可以使用嵌套的 map 解構:

=> (defn show-armors
    [{{head :head,body :body} :armor}]
    (println "頭部:" head)
    (println "身體:" body))
 #'user/show-armors
 
=> (show-armors player-info)
頭部: 巫師帽子
身體: 黑色法袍
nil

我們甚至可以把順序解構和 map 解構結合起來,顯示我們道具欄中的物品:

=> (let [{name :name,coins :coins,[i1 i2 i3] :items} player-info]
  (println name)
  (println coins)
  (println i1 i2 i3))
FFF團火法師
228
生銹的普通匕首 火把 汽油
nil

上面我們說了,解構的時候我們習慣把解構之后的名字取成和 key 一樣,這樣一來,我們就要把一個名字寫兩遍,當元素增多的時候,重復勞動的負擔就無法忍受了。

如果你的 key 都使用 冒號+名字 的格式,你就可以使用 Clojure 提供的另一種方法:

=> (let [{:keys [coins name]} player-info]
          (println "昵稱:" name)
         (println "金幣:" coins))
昵稱: FFF團火法師
金幣: 128
nil

對比以前 [{name :name,coins :coins} player-info] ,新形式使用 :keys 放在了 map 解構頭部,然后跟上一個 vector ,vector 里面的名字要和 key 的名字一致,但順序無要求。

還有許多解構的特殊形式,如解構剩余內(nèi)容,解構的默認值,在今后的學習中在進行說明。


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

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

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