Clojure 學(xué)習(xí)筆記 :2 你好涩惑,集合

Clojure 零基礎(chǔ) 學(xué)習(xí)筆記 數(shù)據(jù)結(jié)構(gòu) 集合


It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures. --- Alan J. Perlis
100 個函數(shù)操作 1 種數(shù)據(jù)結(jié)構(gòu)崖瞭,比 10 個函數(shù)操作 10 種數(shù)據(jù)結(jié)構(gòu)要好 --- Alan J. Perlis

Clojure 提供的基本數(shù)據(jù)結(jié)構(gòu)[1]

  • list
  • vector
  • map
  • set

而上述 4 種數(shù)據(jù)結(jié)構(gòu)我們都稱之為集合(collection)
集合就是把一些東西放在一起沾谓,打個包裹破喻,方便管理职祷。

它們各有特色:

  • list 是 Clojure 中最為簡單的數(shù)據(jù)結(jié)構(gòu)
  • vector 和 list 相似盖灸,它支持高效的隨機訪問
  • map 是一種 “鍵值對” (Key-Value)蚁鳖,在以后的文章中會詳細(xì)介紹這種數(shù)據(jù)結(jié)構(gòu)
  • set 是一種不能出現(xiàn)重復(fù)元素的集合

然而由于篇幅限制和本人水平有限
并不能詳盡的說明每一種集合的每一種用法
本文以 list 和 vector 作簡要介紹
更為詳細(xì)的內(nèi)容可以查閱相關(guān) API[2] 文檔


好吧讀完上面的東西你可能想說

什么玩意兒

沒事兒,文字描述總是很枯燥
我們更傾向于使用代碼來向你介紹
我們使用 list 函數(shù)來創(chuàng)建一個 list (列表)

=> (list "hello" "list")
("hello" "list")

如你所見赁炎, list 函數(shù)的返回值就是一個 list醉箕。
列表里的每一個內(nèi)容稱之為元素,而元素被小括號包圍徙垫,這就構(gòu)成了一個列表琅攘。
此例中的這個 list 包含兩個元素 --- 字符串"hello" 和字符串 "list"

你也許已經(jīng)注意到了松邪,list 的“樣子”看起來很眼熟坞琴,
沒錯,它的書寫方式和 Clojure 表達(dá)式一樣逗抑,Clojure 語言本身就使用這種數(shù)據(jù)結(jié)構(gòu)來表示語言本身剧辐。
使用自身的數(shù)據(jù)結(jié)構(gòu)來表示自身
這種特征我們給它單獨取一個名字,以表示高端邮府,稱之為 “同像性”[3]
又稱 “代碼即數(shù)據(jù)”

(如果你學(xué)習(xí)過編譯相關(guān)的知識荧关,你就能立刻發(fā)現(xiàn)這種做法的意義
Clojure 和所有的 Lisp 一樣,直接使用語法樹作為語言本身
這也是宏(Macro)功能的基礎(chǔ))

Clojure 還提供了一個語法糖[4] --- 單引號 ' 褂傀,它的功能是阻止求值忍啤。
它的非語法糖形式是 quote
quote 的返回值是其后的代碼本身。
我們知道同波,Clojure 里的表達(dá)式都會被求值鳄梅,如果我們使用阻止求值 quote 或者它的語法糖 ', Clojure 就會明白未檩,你想要的不是這個表達(dá)式的值戴尸,而是需要這個表達(dá)式本身(也就是數(shù)據(jù))。

=> (quote ("hello" "list"))
("hello" "list")

=> '("hello" "list")
("hello" "list")

如果不進行阻止求值冤狡,那么由于 “hello” 在括號的第一個位置孙蒙,會被認(rèn)為是一個函數(shù)
然而我們并沒有這個函數(shù),自然也就無法得到值悲雳,此時程序扔出一個錯誤

=> ("hello" "list")
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn
;意為:某個 String(即字符串)無法被作為 IFn(即函數(shù))來使用

說了一大堆挎峦,我們再回到 list
因為同像性合瓢,我們可以使用阻止求值來得到一個 list浑测。也可以使用 list 函數(shù)來得到一個 list。


你可能會發(fā)出這樣的疑問

那么這又有什么屁用呢歪玲?

list 是一些 “元素” 的集合,這些元素可以是任意表達(dá)式掷匠,以先后順序存放在 list 中滥崩。
僅僅是存儲還不夠,我們還可以對這個 list 進行各種操作讹语。
這里介紹幾個常用的操作:

  • firstsecond 函數(shù)分別用來取出集合中的第一個或第二個元素
=> (first '("hello" "list"))
hello

=> (second '("hello" "list"))
list
  • rest 函數(shù)用來返回除了 first 之后的元素钙皮,并以 list 的形式作為返回
=> (rest '("hello" "list"))
("list")
  • nth 函數(shù)用來取出任意指定位置的元素,表示這個位置的索引值放在第三個參數(shù)的位置
=> (nth '("hello" "list") 0) ;取出第 0 號元素
hello

注意顽决,在大多數(shù)程序語言中短条,索引是以 0 開始的
也就是第一個元素的編號為 0,第二個元素的編號為 1 ...

list 可以按照你的直覺進行嵌套:
這樣就使得你可以創(chuàng)造更為復(fù)雜的結(jié)構(gòu)才菠。
我們來看一下如何操作嵌套結(jié)構(gòu)

=> (first '(("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"))
("屠龍寶刀" "點擊就送")

=> (second (first '(("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍")))
點擊就送

我們來講解一下上面第二句代碼
首先我們使用 ' 來得到一個擁有 5 個元素的列表

'(("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"))

這里我們注意茸时,使用 ' 會導(dǎo)致它之后的一個括號中的所有內(nèi)容都被阻止求值
所以在內(nèi)層的列表無需再次使用 '

雖然 list 函數(shù)也可以創(chuàng)建一個列表,但這里如果我們不用 ' 赋访,而是直接使用 list 函數(shù)則會出現(xiàn)錯誤

=> (list ("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍")
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn

為啥呢可都?因為 list 函數(shù)只是把它收到的參數(shù)的來構(gòu)建出一個列表。所以執(zhí)行到需要得到 ("屠龍寶刀" "點擊就送") 的值的時候蚓耽,因為"屠龍寶刀" 在括號的第一個位置渠牲,所以把它當(dāng)作函數(shù)進行處理了,就發(fā)生了上面的錯誤步悠。

回頭再來看這個列表签杈,五個元素分別為

0號 ("屠龍寶刀" "點擊就送")列表也可以作為一個元素(實際上任意表達(dá)式都可以作為元素)
 1號 "激光劍"
 2號 "無盡之刃"
 3號 "傳送槍"
 4號 "物理學(xué)圣劍"

在它的外面我們又套了一層函數(shù) first

(first '(("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"))

我們知道,first 函數(shù)的返回值鼎兽,等于它的參數(shù)的第一個位置所存放的元素答姥,
在這里即為 ("屠龍寶刀" "點擊就送")铣除。

也就是說,
(first '(("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"))
等價于
("屠龍寶刀" "點擊就送")

把這個值作為外層的 second 函數(shù)的參數(shù)踢涌,進行簡單替換通孽。

替換前:

(second (first '(("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍")))

替換后:

(second '("屠龍寶刀" "點擊就送"))

再執(zhí)行 second 函數(shù),取第二個位置睁壁,結(jié)果為 "點擊就送"背苦。
(在某些環(huán)境下,字符串顯示出來可能會帶有雙引號)


下面我們來看一下 vector
我們可以使用 vector 函數(shù)來創(chuàng)建一個 vector

=> (vector "hello" "vec")
["hello" "vec"]

注意到 vector 看起和 list 有所不同潘明,它使用中括號(方括號)而不是小括號來包圍元素行剂。
我們也可以直接使用中括號(方括號)來得到一個 vector:

=> ["hello" "vec"]
["hello" "vec"]

(這里并不需要使用阻止求值,中括號是一種用來創(chuàng)建 vector 的特殊形式钳降,Clojure 能正確的處理它厚宰,并不會和執(zhí)行函數(shù)的小括號造成混淆。)

還記得本文第一句來自于第一位圖靈獎得主 Alan J. Perlis 的格言么遂填?
我們剛學(xué)到的可以用于 list 的函數(shù)也可以用于 vector

=> (first ["hello" "vec"])
hello

=> (second ["hello" "vec"])
vec

=> (rest ["hello" "vec"])
("vec") ;雖然這里我們使用 rest 操作的是 vector铲觉,但 rest 仍然返回 list 形式

=> (nth ["hello" "vec"] 1)
vec

同樣可以嵌套

=> (second (first [["屠龍寶刀" "點擊就送"] "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"]))
點擊就送

我們甚至可以在 vector 里嵌套 list,或者在 list 里面嵌套 vector

=> (second (first ['("屠龍寶刀" "點擊就送") "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"]))
點擊就送

=> (second (first '(["屠龍寶刀" "點擊就送"] "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍")))
點擊就送

這多虧了不同的數(shù)據(jù)結(jié)構(gòu)使用了同一套抽象
我們得以用統(tǒng)一的形式(同一函數(shù))來操作不同的數(shù)據(jù)結(jié)構(gòu)吓坚,讓你好似在操作同一種數(shù)據(jù)結(jié)構(gòu)


list 和 vector 有很多共同點撵幽,就如同已經(jīng)向你展示的一樣
但 vector 擁有一些其它特性

  • 使用 subvec 函數(shù)來取出指定起止位置的內(nèi)容
    后兩個參數(shù)分別表示起止的索引位置(不包括結(jié)束位置元素)
=> (subvec [["屠龍寶刀" "點擊就送"] "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"] 1 3)
["激光劍" "無盡之刃"]

這個例子給出的起止位置為 1 3,不包括結(jié)束位置的元素礁击,也就是從 1 開始取到 2
結(jié)果組成一個 vector 作為返回值

  • 使用 assoc 函數(shù)來 “改變” 指定位置的內(nèi)容
    這個函數(shù)的后兩個參數(shù)分別是要 “改變” 的元素的位置和 “改變” 后的值盐杂。
    返回值是 “改變” 后的結(jié)果。
=> (assoc [["屠龍寶刀" "點擊就送"] "激光劍" "無盡之刃" "傳送槍" "物理學(xué)圣劍"] 1 "lightsaber")
[["屠龍寶刀" "點擊就送"]
 "lightsaber"
 "無盡之刃"
 "傳送槍"
 "物理學(xué)圣劍"]

注意我們使用了帶引號的 “改變”哆窿,這表示 assoc 函數(shù)實際上并沒有對原來的 vector 做出任何改變链烈。
這個特性會在之后的文章中進行說明。

  • nth 語意可以直接被使用
=> (nth ["hello" "vec"] 1)
vec

=> (["hello" "vec"] 1) ;比上面使用 nth 更方便
vec

其實 ["hello" "vec"] 本身就是個函數(shù)挚躯,所以可以把它放在函數(shù)的位置
這個函數(shù)的功能和 nth 一樣强衡,所以我們可以方便的從 vector 里面取數(shù)據(jù)


你可以把你學(xué)到 first second rest 用于 map 和 set
但你不能把 nth 用于 map

原因是 map 沒有實現(xiàn) Indexed,而 nth 只能作用于實現(xiàn)了 Indexed 的集合码荔。
first second rest 可以作用于實現(xiàn)了 Sequence 的集合食侮,所有的 Clojure 集合都實現(xiàn)了 Sequence。

這些關(guān)于集合的抽象實現(xiàn)內(nèi)容并不需要在現(xiàn)在就掌握目胡,感興趣可以自行查詢锯七。

map 會在之后的章節(jié)中做詳細(xì)介紹,它是 Clojure 中非常實用的一種數(shù)據(jù)結(jié)構(gòu)

如果你在閱讀本文時感到吃力誉己,你可以先把文中的代碼在你的機器上運行一下
觀察運行結(jié)果眉尸,然后試著更改參數(shù),看看返回結(jié)果是否滿足你的猜想
待你基本了解工作效果之后,再次閱讀本文
這種學(xué)習(xí)方式會幫助你更好地理解本文(或者其它程序設(shè)計類教程)

除了本文介紹的方法之外噪猾,操作集合的函數(shù)還有很多
你可以訪問 http://clojuredocs.org/ 查閱 API[2] 的小例子
也可以訪問官方網(wǎng)站 http://clojure.github.io/clojure/ 來直接查閱官方 API 說明
英文苦手們可以訪問中文翻譯項目 http://clojure-api-cn.readthedocs.io/en/latest/


  1. 數(shù)據(jù)結(jié)構(gòu):簡單來說霉祸,就是如何“擺放”值,和如何對值進行操作的一種抽象 ?

  2. Application Programming Interface袱蜡,即應(yīng)用編程接口丝蹭。是一種描述函數(shù)如何工作的描述文檔 ? ?

  3. 同像性并不是 Clojure 的專利,其它很多語言也具有同像性坪蚁,包括所有 Lisp 方言奔穿、機器語言(匯編語言)、Prolog 等 ?

  4. 語法糖:便于程序員書寫的一種簡化語法的 “甜甜的” 東西 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敏晤,一起剝皮案震驚了整個濱河市贱田,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嘴脾,老刑警劉巖男摧,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異译打,居然都是意外死亡耗拓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門奏司,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乔询,“玉大人,你說我怎么就攤上這事结澄。” “怎么了岸夯?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵麻献,是天一觀的道長。 經(jīng)常有香客問我猜扮,道長勉吻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任旅赢,我火速辦了婚禮齿桃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煮盼。我一直安慰自己短纵,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布僵控。 她就那樣靜靜地躺著香到,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悠就,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天千绪,我揣著相機與錄音,去河邊找鬼梗脾。 笑死荸型,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的炸茧。 我是一名探鬼主播瑞妇,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宇立!你這毒婦竟也來了踪宠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤妈嘹,失蹤者是張志新(化名)和其女友劉穎柳琢,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體润脸,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡柬脸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了毙驯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒堕。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖爆价,靈堂內(nèi)的尸體忽然破棺而出垦巴,到底是詐尸還是另有隱情,我是刑警寧澤铭段,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布骤宣,位于F島的核電站,受9級特大地震影響序愚,放射性物質(zhì)發(fā)生泄漏憔披。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一爸吮、第九天 我趴在偏房一處隱蔽的房頂上張望芬膝。 院中可真熱鬧,春花似錦形娇、人聲如沸锰霜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锈遥。三九已至纫事,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間所灸,已是汗流浹背丽惶。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留爬立,地道東北人钾唬。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像侠驯,于是被迫代替她去往敵國和親抡秆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容