Clojure 学习笔记 :7 map --- 可能是最有用的数据结构

Clojure 零基础 学习笔记 map


map 是一种映射关系

map 长啥样

这次介绍的 map 不是高阶函数 map ,而是我们在第二篇文章《你好,集合》里面曾经提到名字的一种 Clojure 提供的数据结构。高阶函数 map 像是一个动词,表示一种函数和一系列参数之间的“映射”动作。而数据结构 map 是一个名词,它表示“关键字”和“值”之间的对应关系。也就是业界通常简称的“键值对”、key-value。

那到底长啥样呢?我们来写几个 map

{"键" "值"}
{"关键字1" "值1" "关键字2" "值2"} ;不推荐这种使用空格来间隔的写法
{:name "blindingdark", :age "不告诉你"} ;如果写在一行,每一组键值对之间用逗号间隔
{:name "FFF团火法师"
 :level 32
 :coins 128
 :items ["生锈的普通匕首" "火把" "汽油"]} ;不过我们更建议在每组键值对之间换行

接触过 JSON 的同学应该非常熟悉这种形式。
使用花括号 {} 来包围一组键值对,键和值可以是任意表达式(如字符串,数字,list,vector)。每一对键值对中,键和值用空格隔开,不同的键值对之间使用逗号或者空格或者换行隔开。

上面的例子中也对间隔符号进行了说明,虽然 Clojure 允许多种个性化的风格,但无论哪种风格,总是要保持良好的可读性。

不过通常我们使用一种特别的形式来表示“键”,即使用 冒号+关键字名 的形式来表示。你马上就会在接下来的内容中看到,如何使用这种形式更方便的访问 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 函数来得到某个 key 的 value。

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

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

=> (get player-info :level)
32

=> (get player-info :sex)
nil

=> (get player-info :sex "没有找到这个属性")
"没有找到这个属性"

很容易就可以总结出 get 函数的用法:

  1. 第一个参数是要访问的 map
  2. 第二个参数是要访问的 map 中的 key
  3. 第三个参数可选,作用是设置一个默认值,如果 map 中访问的 key 不存在,则返回这个参数的值
  4. 正常情况下返回 key 所对应的 value
  5. 如果第三个参数未填写,那么一旦 map 中没有所访问的 key,则返回 nil

get 函数不仅能访问 map,有关它的其他用法请查询相关 api。

如果你使用了 冒号+关键字名 这种形式来表示一个 key,那么我们就可以更加简便的访问 map 中的 value 了。

=> (:name player-info)
"FFF团火法师"

=> (:sex player-info)
nil

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

我们发现, 冒号+关键字名 这种形式的表示,竟然能放在括号的第一个位置,这说明它也是一个函数。所以这种奇特的函数的使用方法是这样的:

  1. 第一个参数是要访问的 map
  2. 第二个参数可选(和 get 函数的第三个参数作用一致)
  3. 在 map 中寻找和 key 函数本身相同的 key(其它特性与 get 函数相似)

除了读取 map 中的内容,我们还可以使用 assocdissoc 来增加和删除 map 中的元素。

比如我们的火法师成功的烧烧烧了一对情侣,获得 100 金币,获得称号:大师级火焰掌控

首先,我们使用 assoc 函数向 player-info 添加一个键值对 称号-称号名

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

还记得我们之前说过的使用 def 声明的值是不可变的 么?所以此时我们访问 player-info 会发现它并没有改变。

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

所以我们得重新声明:

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

如果 assoc 函数所添加的 key 已经存在,就会使用新值来覆盖。所以我们在此使用它来改变金币的数量。

=> (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 函数。比如我们删除称号:

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

如果我们想得到所有的 key 或者得到所有的 value,可以用 keys 函数和 vals 函数:

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

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

map 嵌套

map 中可以嵌套 map(或者嵌套任何你想要的表达式),这是非常自然的做法,你可以一层一层的来组装你的数据,以便更好的描述你所需要的内容。

比如我们可以给我们的 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 函数中的解构样式(点这里可以复习如何解构参数列表

让我们写一个函数,来显示我们的火法师的姓名和等级:

=> (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 的名字一致,但顺序无要求。

还有许多解构的特殊形式,如解构剩余内容,解构的默认值,在今后的学习中在进行说明。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容