映射

Maps 保存从key到value的a对应关系 — key和value都可以是任意对象。key-value 组合被以一种可以按照key的顺序高效获取的方式保存着。

下面是创建map的一些方法, 其中逗号是为了提高可读性的,它是可选的,解析的时候会被当作空格忽略掉的。

(def popsicle-map
  (hash-map :red :cherry, :green :apple, :purple :grape))
(def popsicle-map
  {:red :cherry, :green :apple, :purple :grape}) ; same as previous
(def popsicle-map
  (sorted-map :red :cherry, :green :apple, :purple :grape))

Map可以作为它的key的函数,同时如果key是keyword的话,那么key也可以作为map的函数。下面是三种获取:green所对应的值的方法:

(get popsicle-map :green)
(popsicle-map :green)
(:green popsicle-map)

contains? 方法可以操作 sets 和 maps. 当被用在map上的时候,它返回map是否包含给定的key. keys 函数返回map里面的所有的key的集合. vals 函数返回map里面所有值的集合. 看例子:

(contains? popsicle-map :green) ; -> true
(keys popsicle-map) ; -> (:red :green :purple)
(vals popsicle-map) ; -> (:cherry :apple :grape)

assoc 函数可以操作 maps 和 vectors. 当被用在map上的时候,它会创建一个新的map, 同时添加任意对新的name-value pair, 如果某个给定的key已经存在了,那么它的值会被更新。看例子:

(assoc popsicle-map :green :lime :blue :blueberry)
; -> {:blue :blueberry, :green :lime, :purple :grape, :red :cherry}

dissoc 创建一个新的map, 同时忽略掉给定的那么些key, 看例子:

(dissoc popsicle-map :green :blue) ; -> {:purple :grape, :red :cherry}

我们也可以把map看成一个简单的集合,集合里面的每个元素是一个pair: name-value: clojure.lang.MapEntry 对象. 这样就可以和doseq跟destructuring一起使用了, 它们的作用都是更简单地来遍历map, 我们会在后面详细地介绍这些函数. 下面的这个例子会遍历 popsicle-map 里面的所有元素,把key bind到 color, 把value bind到 flavor。 name函数返回一个keyword的字符串名字。

(doseq [[color flavor] popsicle-map]
  (println (str "The flavor of " (name color)
    " popsicles is " (name flavor) ".")))

上面的代码的输出是这样的:

The flavor of green popsicles is apple.
The flavor of purple popsicles is grape.
The flavor of red popsicles is cherry.

select-keys 函数接收一个map对象,以及一个key的集合的参数,它返回这个集合里面key在那个集合里面的一个子map。看例子:

(select-keys popsicle-map [:red :green :blue]) ; -> {:green :apple, :red :cherry}

conj 函数添加一个map里面的所有元素到另外一个map里面去。如果目标map里面的key在源map里面也有,那么目标map的值会被更新成源map里面的值。

map里面的值也可以是一个map, 而且这样嵌套无限层。获取嵌套的值是非常简单的。同样的,更新一个嵌套的值也是很简单的。

为了证明这个, 我们会创建一个描述人(person)的map。其中内嵌了一个表示人的地址的map,同时还有一个叫做employer的内嵌map。

(def person {
  :name "Mark Volkmann"
  :address {
    :street "644 Glen Summit"
    :city "St. Charles"
    :state "Missouri"
    :zip 63304}
  :employer {
    :name "Object Computing, Inc."
    :address {
      :street "12140 Woodcrest Executive Drive, Suite 250"
      :city "Creve Coeur"
      :state "Missouri"
      :zip 63141}}})

get-in 函数、宏 -> 以及函数 reduce 都可以用来获得内嵌的key. 下面展示了三种获取这个人的employer的address的city的值的方法:

(get-in person [:employer :address :city])
(-> person :employer :address :city) ; explained below
(reduce get person [:employer :address :city]) ; explained below

-> 我们也称为 “thread” 宏, 它本质上是调用一系列的函数,前一个函数的返回值作为后一个函数的参数. 比如下面两行代码的作用是一样的:

(f1 (f2 (f3 x)))
(-> x f3 f2 f1)

在名字空间 clojure.contrib.core 里面还有个 -?>宏, 它会马上返回nil, 如果它的调用链上的任何一个函数返回nil (short-circiut)。这会避免抛出 NullPointerException 异常。

reduce 函数接收一个需要两个参数的函数, 一个可选的value以及一个集合。它会以value以及集合的第一个元素作为参数来调用给定的函数(如果指定了value的话), 要么以集合的第一个元素以及第二个元素为参数来调用给定的函数(如果没有指定value的话)。接着就以这个返回值以及集合里面的下一个元素为参数来调用给定的函数,知道集合里面的元素都被计算了 — 最后返回一个值. 这个函数与ruby里面的 inject 以及Haskell里面的 foldl 作用是一样的。

assoc-in 函数可以用来修改一个内嵌的key的值。看下面的例子把person的employer->address->city修改成Clayton了。

(assoc-in person [:employer :address :city] "Clayton")

update-in 函数也是用来更新给定的内嵌的key对应的值,只是这个新值是通过一个给定的函数来计算出来。下面的例子里面会把person的employer->address->zip改成旧的zip + “-1234″。看例子:

(update-in person [:employer :address :zip] str "-1234") ; using the str function