(def players #{"Alice", "Bob", "Kelly"})
集合就像数学集合 - 无序且没有重复。集合非常适合有效地检查集合是否包含元素,或删除任何任意元素。
(def players #{"Alice", "Bob", "Kelly"})
disj
(“分离”)函数用于从集合中删除一个或多个元素。
user=> players
#{"Alice" "Kelly" "Bob"}
user=> (disj players "Bob" "Sal")
#{"Alice" "Kelly"}
正如您所见,disj
不存在的元素是可以的。
映射通常用于两种目的 - 管理键到值的关联以及表示域应用程序数据。第一个用例在其他语言中通常被称为字典或哈希映射。
映射表示为交替的键和值,用 {
和 }
包围。
(def scores {"Fred" 1400
"Bob" 1240
"Angela" 1024})
当 Clojure 在 REPL 中打印映射时,它将在每个键/值对之间放置 `,’s。这些仅用于可读性 - 逗号在 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
您不应该直接调用映射,除非您能保证它是非空的。
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
函数在映射中找到键/值条目,而不仅仅是值。
您也可以仅获取映射中的键或仅获取值。
user=> (keys scores)
("Fred" "Bob" "Angela")
user=> (vals scores)
(1400 1240 1024)
虽然映射是无序的,但有一个保证,即 keys、vals 和其他按“序列”顺序遍历的函数将始终按相同的顺序遍历特定映射实例的条目。
zipmap
函数可用于将两个序列(键和值)“压缩”成一个映射。
user=> (def players #{"Alice" "Bob" "Kelly"})
#'user/players
user=> (zipmap players (repeat 0))
{"Kelly" 0, "Bob" 0, "Alice" 0}
使用 Clojure 的序列函数(我们尚未讨论)还有多种其他方法可以构建映射。稍后再回来看看它们!
;; 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
函数可用于将多个映射合并成一个映射。
user=> (def new-scores {"Angela" 300 "Jeff" 900})
#'user/new-scores
user=> (merge scores new-scores)
{"Fred" 1400, "Bob" 1240, "Jeff" 900, "Angela" 300}
我们在这里合并了两个映射,但您也可以传递更多映射。
如果两个映射都包含相同的键,则最右边的映射获胜。或者,您可以使用 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}
在发生冲突的情况下,该函数将对两个值进行调用以获取新值。
当我们需要用预先知道的相同字段集来表示许多域信息时,您可以使用具有关键字键的映射。
(def person
{:first-name "Kelly"
:last-name "Keen"
:age 32
:occupation "Programmer"})
由于这是一个映射,因此我们之前讨论过的通过键查找值的方法也适用。
user=> (get person :occupation)
"Programmer"
user=> (person :occupation)
"Programmer"
但实际上,获取此使用情况的字段值的的最常用方法是调用关键字。就像映射和集合一样,关键字也是函数。当调用关键字时,它会在传递给它的关联数据结构中查找自身。
user=> (:occupation person)
"Programmer"
关键字调用也采用可选的默认值。
user=> (:favorite-color person "beige")
"beige"
由于这是一个映射,我们可以使用 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"