(def players #{"Alice", "Bob", "Kelly"})
集合就像數學集合 - 無序且沒有重複。集合非常適合有效檢查集合是否包含元素,或移除任何任意元素。
(def players #{"Alice", "Bob", "Kelly"})
disj
(「分離」)函數用於從集合中移除一個或多個元素。
user=> players
#{"Alice" "Kelly" "Bob"}
user=> (disj players "Bob" "Sal")
#{"Alice" "Kelly"}
如你所見,從集合中移除不存在的元素並無妨。
映射通常用於兩個目的 - 管理鍵值關聯以及表示網域應用程式資料。第一個使用案例在其他語言中通常稱為字典或雜湊映射。
映射表示為交替的鍵和值,並以 {
和 }
包圍。
(def scores {"Fred" 1400
"Bob" 1240
"Angela" 1024})
當 Clojure 在 REPL 中列印映射時,它會在每個鍵/值對之間加上逗號。這些純粹用於可讀性 - 逗號在 Clojure 中視為空白。在有助於理解的情況下,請隨時使用它們!
;; same as the last one!
(def scores {"Fred" 1400, "Bob" 1240, "Angela" 1024})
使用 assoc
(「關聯」的簡寫)函數將新值新增到映射中
user=> (assoc scores "Sally" 0)
{"Angela" 1024, "Bob" 1240, "Fred" 1400, "Sally" 0}
如果 assoc
中使用的鍵已存在,則會取代該值。
user=> (assoc scores "Bob" 0)
{"Angela" 1024, "Bob" 0, "Fred" 1400}
有幾種方法可以在映射中查詢值。最明顯的是 get
函數
user=> (get scores "Angela")
1024
當問題中的映射被視為常數查詢表時,通常會呼叫映射本身,並將其視為函數
user=> (def directions {:north 0
:east 1
:south 2
:west 3})
#'user/directions
user=> (directions :north)
0
除非你可以保證映射不會為 nil,否則不應直接呼叫映射
user=> (def bad-lookup-map nil)
#'user/bad-lookup-map
user=> (bad-lookup-map :foo)
Execution error (NullPointerException) at user/eval154 (REPL:1).
null
如果你想執行查詢,並在找不到鍵時回退至預設值,請將預設值指定為額外的參數
user=> (get scores "Sam" 0)
0
user=> (directions :northwest -1)
-1
使用預設值也有助於區分遺失的鍵和具有 nil
值的現有鍵。
還有兩個函數有助於檢查映射是否包含某個項目。
user=> (contains? scores "Fred")
true
user=> (find scores "Fred")
["Fred" 1400]
contains?
函數是檢查包含的謂詞。find
函數會在映射中找到鍵/值項目,而不仅仅是值。
你也可以只取得 map 中的鍵或值
user=> (keys scores)
("Fred" "Bob" "Angela")
user=> (vals scores)
(1400 1240 1024)
雖然 map 是無序的,但可以保證鍵、值和其他以「順序」順序運行的函式,將永遠以相同的順序遍歷特定 map 實例中的項目。
zipmap
函式可用於將兩個順序(鍵和值)「壓縮」成一個 map
user=> (def players #{"Alice" "Bob" "Kelly"})
#'user/players
user=> (zipmap players (repeat 0))
{"Kelly" 0, "Bob" 0, "Alice" 0}
還有許多其他方法可以使用 Clojure 的順序函式建立 map(我們尚未討論)。稍後再回來討論這些方法!
;; with map and into
(into {} (map (fn [player] [player 0]) players))
;; with reduce
(reduce (fn [m player]
(assoc m player 0))
{} ; initial value
players)
merge
函式可用於將多個 map 合併成一個單一 map
user=> (def new-scores {"Angela" 300 "Jeff" 900})
#'user/new-scores
user=> (merge scores new-scores)
{"Fred" 1400, "Bob" 1240, "Jeff" 900, "Angela" 300}
我們在此合併了兩個 map,但你也可以傳遞更多 map。
如果兩個 map 包含相同的鍵,最右邊的 map 會獲勝。或者,你可以使用 merge-with
提供一個函式,在發生衝突時呼叫該函式
user=> (def new-scores {"Fred" 550 "Angela" 900 "Sam" 1000})
#'user/new-scores
user=> (merge-with + scores new-scores)
{"Sam" 1000, "Fred" 1950, "Bob" 1240, "Angela" 1924}
在發生衝突時,函式會針對兩個值呼叫,以取得新的值。
當我們需要使用一組已知且相同的欄位表示許多網域資訊時,你可以使用具有關鍵字鍵的 map。
(def person
{:first-name "Kelly"
:last-name "Keen"
:age 32
:occupation "Programmer"})
由於這是一個 map,因此我們之前討論過用鍵查詢值的那些方法也適用
user=> (get person :occupation)
"Programmer"
user=> (person :occupation)
"Programmer"
但實際上,取得此用途的欄位值的常見方法是呼叫關鍵字。就像 map 和集合一樣,關鍵字也是函式。當呼叫關鍵字時,它會在傳遞給它的關聯式資料結構中查詢自己。
user=> (:occupation person)
"Programmer"
關鍵字呼叫也需要一個選用的預設值
user=> (:favorite-color person "beige")
"beige"
由於這是一個 map,我們可以使用 assoc
來新增或修改欄位
user=> (assoc person :occupation "Baker")
{:age 32, :last-name "Keen", :first-name "Kelly", :occupation "Baker"}
使用 dissoc 來移除欄位
user=> (dissoc person :age)
{:last-name "Keen", :first-name "Kelly", :occupation "Programmer"}
常見到實體巢狀在其他實體中
(def company
{:name "WidgetCo"
:address {:street "123 Main St"
:city "Springfield"
:state "IL"}})
你可以使用 get-in
來存取巢狀實體中任何層級的欄位
user=> (get-in company [:address :city])
"Springfield"
您也可以使用 assoc-in
或 update-in
來修改巢狀實體
user=> (assoc-in company [:address :street] "303 Broadway")
{:name "WidgetCo",
:address
{:state "IL",
:city "Springfield",
:street "303 Broadway"}}
使用記錄是使用映射的替代方案。記錄是專門為此用例而設計的,通常具有更好的效能。此外,它們有一個名為「類型」的名稱,可用於多型行為(稍後會詳細說明)。
記錄使用記錄實例的欄位名稱清單來定義。這些將在每個記錄實例中視為關鍵字鍵。
;; Define a record structure
(defrecord Person [first-name last-name age occupation])
;; Positional constructor - generated
(def kelly (->Person "Kelly" "Keen" 32 "Programmer"))
;; Map constructor - generated
(def kelly (map->Person
{:first-name "Kelly"
:last-name "Keen"
:age 32
:occupation "Programmer"}))
記錄的使用方式幾乎與映射相同,但它們無法像映射一樣被呼叫為函式。
user=> (:occupation kelly)
"Programmer"