第六章 列表數(shù)據(jù)結(jié)構(gòu)
6.1 導(dǎo)語(yǔ)
本章會(huì)展示更多列表處理函數(shù)和列表如何被應(yīng)用在其他數(shù)據(jù)結(jié)構(gòu)中,比如集合何荚,表格和樹。Common Lisp相比于其他語(yǔ)言的強(qiáng)處之一就是,提供了很多支持這些數(shù)據(jù)結(jié)構(gòu)的內(nèi)建函數(shù)仰冠。Lisp程序員就哭了一很快的聚焦在他想解決的問(wèn)題上。Pascal或者C程序員在解決實(shí)際問(wèn)題之前必須先跳出來(lái)蝶糯,先去實(shí)現(xiàn)一個(gè)類似于Lisp提供的系統(tǒng)洋只,例如連接列表的原語(yǔ),字符數(shù)據(jù)結(jié)構(gòu)昼捍,存儲(chǔ)的分配等等识虚。
在本章我們會(huì)比第二章的時(shí)候處理列表更加復(fù)雜一些。不止會(huì)塔倫一些列表處理函數(shù)的行為妒茬,而且還會(huì)看到他們內(nèi)部的工作担锤。我們先預(yù)先準(zhǔn)備一下,復(fù)習(xí)2.17小節(jié)學(xué)習(xí)到的點(diǎn)對(duì)表達(dá)式乍钻。如果你還沒(méi)有閱讀先前的進(jìn)階話題單元肛循,現(xiàn)在去讀讀吧。
6.2 括號(hào)表達(dá)式VS內(nèi)存單元表達(dá)式
用括號(hào)表達(dá)式來(lái)表示列表是很方便的银择,但是也會(huì)產(chǎn)生誤導(dǎo)多糠。使用括號(hào)表達(dá)式表示的列表是對(duì)稱的:由一個(gè)左括號(hào)開始并結(jié)束于一個(gè)右括號(hào)。因此浩考,cons函數(shù)處理參數(shù)也是對(duì)稱的夹孔,cons函數(shù)在一個(gè)列表前加上一個(gè)元素,如下:
>(cons ’w ’(x y z)) → (w x y z)
為什么我們不能在一個(gè)列表的尾部加上一個(gè)元素呢析孽?初學(xué)者這樣嘗試之后會(huì)被結(jié)果驚呆了:
>(cons ’(a b c) ’d) → ((a b c) . d)
如果我們?cè)诶ㄌ?hào)表達(dá)式中析蝴,訪問(wèn)一個(gè)列表的左端和右端的區(qū)別沒(méi)有理由這么巨大。但是切換到內(nèi)存單元表達(dá)式來(lái)看绿淋,區(qū)別就非常巨大了闷畸。列表是由指針構(gòu)成的單向鏈條。對(duì)于列表來(lái)說(shuō)我們很容易在列表前面添加一個(gè)項(xiàng)目是因?yàn)橥讨停覀儗?shí)際上做的是創(chuàng)造一個(gè)新的單元并且白cdr指向現(xiàn)有的列表佑菩,只是這樣盾沫。如果將W和(X Y Z)輸入cons函數(shù),結(jié)果就是一個(gè)新的內(nèi)存單元殿漠,它的car指向w赴精,cdr指向原有的列表,如下所示绞幌。雖然我們把結(jié)果寫成這樣(W X Y Z)蕾哟,但是實(shí)際上也可以用點(diǎn)對(duì)表達(dá)式來(lái)表示(W . (X Y Z))。
列表(A B C)和D輸入cons函數(shù)的時(shí)候莲蜘,新內(nèi)存單元的car是指向原有列表(A B C)谭确,cdr是指向符號(hào)D。結(jié)果就是((A B C) . D)票渠,用括號(hào)表達(dá)式看上去是很奇怪逐哈。這個(gè)點(diǎn)的存在是絕對(duì)必須的,因?yàn)檫@個(gè)列表是由其他元符號(hào)而不是nil來(lái)結(jié)尾问顷。用內(nèi)存單元表達(dá)式看起來(lái)是這樣:
由于原先的列表cdr已經(jīng)指向了nil昂秃,所以通過(guò)新建內(nèi)存單元的的方法來(lái)給列表尾部加上一個(gè)元素的方法是不可行的。更加復(fù)雜的技術(shù)將會(huì)被引用杜窄,其中一個(gè)方法在下一個(gè)小節(jié)會(huì)被介紹肠骆。
6.3 append函數(shù)
append函數(shù)接受兩個(gè)列表作為輸入,返回一個(gè)包括所有元素的列表塞耕,第一個(gè)列表的元素之后是第二個(gè)列表的元素哗戈。
> (append ’(friends romans) ’(and countrymen))
(FRIENDS ROMANS AND COUNTRYMEN)
> (append ’(l m n o) ’(p q r))
(L M N O P Q R)
如果append函數(shù)的輸入列表是空列表,那么結(jié)果就等于其他輸入荷科,給一個(gè)列表追加空列表就相當(dāng)于在一個(gè)數(shù)字上加上0。
> (append ’(april showers) nil)
(APRIL SHOWERS)
> (append nil ’(bring may flowers))
(BRING MAY FLOWERS)
> (append nil nil)
NIL
append函數(shù)對(duì)于嵌套列表也是起作用的纱注,他只著眼于頂層括號(hào)畏浆,所以不必在意輸入是不是嵌套列表。
> (append ’((a 1) (b 2)) ’((c 3) (d 4)))
((A 1) (B 2) (C 3) (D 4))
append函數(shù)不會(huì)改變?nèi)魏巫兞炕蛘攥F(xiàn)有內(nèi)存單元的值狞贱,所以也被稱為非破壞性函數(shù)刻获。
> (setf who ’(only the good))
(ONLY THE GOOD)
> (append who ’(die young))
(ONLY THE GOOD DIE YOUNG)
> who
(ONLY THE GOOD) The value of WHO is unchanged.
(吐槽:作者這是滿滿的惡意么?好人死得早瞎嬉?搜索一下才發(fā)現(xiàn)是一首77年老歌的名字)
append函數(shù)的兩個(gè)輸入看上去好像是對(duì)稱的蝎毡,但這只是括號(hào)表達(dá)式帶來(lái)的一個(gè)假象。append函數(shù)對(duì)待兩個(gè)輸入是完全不同的氧枣。當(dāng)往列表(D E)后面追加列表(A B C)的時(shí)候沐兵,append賦值第一個(gè)輸入但是不復(fù)制第二個(gè)。第一個(gè)輸入的最后一個(gè)內(nèi)存單元的cdr指向了第二個(gè)輸入便监,返回一個(gè)復(fù)制的列表的指針扎谎。如圖6-1所示碳想。
對(duì)append函數(shù)的工作原理描述過(guò)后,就解釋了為什么在第一個(gè)輸入不是列表的時(shí)候會(huì)報(bào)錯(cuò)毁靶,但是第二個(gè)輸入不是列表的時(shí)候就ok胧奔。
>(append ’a ’(b c d)) → Error! A is not a list.
>(append ’(w x y) ’z) → (W X Y . Z)
append想要復(fù)制第一個(gè)輸入的內(nèi)存單元,但由于第一個(gè)輸入A并不是一個(gè)列表预吆,所以報(bào)錯(cuò)了龙填。但是當(dāng)往列表(W X Y)上追加Z的時(shí)候,append可以復(fù)制他的第一個(gè)輸入然后最后一個(gè)內(nèi)存單元的cdr會(huì)指向第二個(gè)輸入拐叉,所以不會(huì)報(bào)錯(cuò)岩遗。因?yàn)榈诙€(gè)輸入不是一個(gè)列表,所以結(jié)果看上去還是有點(diǎn)奇怪巷嚣。
現(xiàn)在我們需要解決的問(wèn)題就是喘先,把一個(gè)元素加載一個(gè)列表的后面,如果我們先把元素做成一個(gè)列表廷粒,那么就可以使用append來(lái)解決這個(gè)問(wèn)題了窘拯。
>(append ’(a b c) ’(d)) → (A B C D)
(defun add-to-end (x e)
"Adds element E to the end of list X."
(append x (list e)))
(add-to-end ’(a b c) ’d) → (A B C D)
比較cons,list和append
一開始Lisp程序員會(huì)在cons坝茎,list和append函數(shù)之間的區(qū)分上有所困惑涤姊,因?yàn)檫@三個(gè)函數(shù)都有構(gòu)造列表數(shù)據(jù)結(jié)構(gòu)的功能。這里是一個(gè)對(duì)三個(gè)函數(shù)的簡(jiǎn)單回顧:
- cons函數(shù)創(chuàng)造一個(gè)新的內(nèi)存單元嗤放,經(jīng)常被使用在列表前面需要增加一個(gè)元素的時(shí)候思喊。
- list函數(shù)通過(guò)接受任意數(shù)量的輸入來(lái)創(chuàng)造一個(gè)新的列表,而且在最后一個(gè)元素的內(nèi)存單元以nil結(jié)束次酌。每一個(gè)元素的car指向?qū)?yīng)的輸入恨课。
- append函數(shù)將列表合成在一起。通過(guò)復(fù)制第一個(gè)輸入岳服,然后使第一個(gè)輸入的最后一個(gè)單元的cdr指向第二個(gè)輸入剂公。如果第一個(gè)輸入不是列表的話將會(huì)出現(xiàn)錯(cuò)誤。
現(xiàn)在我們拉進(jìn)一步比較他們吊宋。首先考慮一種纲辽,第一個(gè)輸入是符號(hào),第二個(gè)輸入是列表的情況:
> (cons ’rice ’(and beans))
(RICE AND BEANS)
> (list ’rice ’(and beans))
(RICE (AND BEANS))
> (append ’rice ’(and beans))
Error: RICE is not a list.
然后我們看看兩個(gè)輸入都是列表的情況:
> (cons ’(here today) ’(gone tomorrow))
((HERE TODAY) GONE TOMORROW)
> (list ’(here today) ’(gone tomorrow))
((HERE TODAY) (GONE TOMORROW))
> (append ’(here today) ’(gone tomorrow))
(HERE TODAY GONE TOMORROW)
最后我們來(lái)看看第一個(gè)輸入是列表璃搜,第二個(gè)輸入是符號(hào)的情況拖吼。這是最讓人困惑的情況,你必須用內(nèi)存單元表達(dá)式來(lái)再想想这吻。
> (cons ’(eat at) ’joes)
((EAT AT) . JOES)
> (list ’(eat at) ’joes)
((EAT AT) JOES)
> (append ’(eat at) ’joes)
(EAT AT . JOES)
為了更多的而開發(fā)你對(duì)這三個(gè)函數(shù)的直覺(jué)吊档,嘗試一下使用sdraw工具來(lái)驗(yàn)證上面的例子,lisp toolkit中的sdraw工具是用來(lái)畫內(nèi)存單元表達(dá)式圖的唾糯。
6.5 更多關(guān)于列表的函數(shù)
lisp提供了很多用來(lái)操作列表的簡(jiǎn)單函數(shù)籍铁,我們已經(jīng)討論過(guò)的有cons涡上,list郁季,append和length丁频。他們之中有些必須復(fù)制它的輸入已维,有些則不需要石挂。接下來(lái)請(qǐng)看這樣設(shè)置的理由野哭。
6.5.1 reverse(反轉(zhuǎn))
reverse函數(shù)提供一個(gè)列表的反轉(zhuǎn)挑庶。
> (reverse ’(one two three four five))
(FIVE FOUR THREE TWO ONE)
> (reverse ’(l i v e))
(E V I L)
> (reverse ’live)
Error: Wrong type input.
> (reverse ’((my oversight)
(your blunder)
(his negligence)))
((HIS NEGLIGENCE) (YOUR BLUNDER) (MY OVERSIGHT))
請(qǐng)注意reverse函數(shù)只是反轉(zhuǎn)頂層括號(hào)的元素拿霉。嵌套列表里的作為元素的列表不會(huì)被操作筋搏。關(guān)于reverse函數(shù)的另一點(diǎn)就是他不支持符號(hào)的操作同云。列表(L I V E)的反轉(zhuǎn)是列表(E V I L)糖权,但是符號(hào)live的反轉(zhuǎn)會(huì)返回一個(gè)類型輸入錯(cuò)誤。
就像append一樣炸站,reverse函數(shù)也是非破壞性的星澳。他會(huì)復(fù)制輸入而不是改變輸入。
> (setf vow ’(to have and to hold))
(TO HAVE AND TO HOLD)
(reverse vow)
(HOLD TO AND HAVE TO)
vow
(TO HAVE AND TO HOLD)
我們可以像下面一樣使用reverse函數(shù)來(lái)給一個(gè)列表的最后加上一個(gè)元素旱易。假設(shè)禁偎,我們想要給列表(A B C)加上元素D。列表(A B C)的反轉(zhuǎn)就是(C B A)阀坏。把符號(hào)D加到這個(gè)反轉(zhuǎn)列表的前面如暖,再一次反轉(zhuǎn),就得到了列表(A B C D)忌堂。
(defun add-to-end (x y)
(reverse (cons y (reverse x))))
(add-to-end ’(a b c) ’d) → (a b c d)
現(xiàn)在你知道了兩種在列表后面加上元素的方案盒至。append方案和雙reverse方案,相比之下士修,append方案會(huì)更好一些枷遂,因?yàn)殡preverse方案會(huì)進(jìn)行兩次復(fù)制,append方案會(huì)更有效率一些棋嘲。在本章的結(jié)尾酒唉,我們會(huì)在進(jìn)階話題中進(jìn)一步討論效率問(wèn)題。
6.5.2 nth和nthcdr
nthcdr函數(shù)返回的是列表的連續(xù)第n個(gè)的cdr封字。當(dāng)然了我們輸入0個(gè)的話,就會(huì)返回原來(lái)的列表耍鬓,如果要的個(gè)數(shù)多過(guò)列表的長(zhǎng)度阔籽,就會(huì)返回NIL。
(nthcdr 0 ’(a b c)) → (a b c)
(nthcdr 1 ’(a b c)) → (b c)
(nthcdr 2 ’(a b c)) → (c)
(nthcdr 3 ’(a b c)) → nil
使用大于3的數(shù)字輸入并不會(huì)引發(fā)錯(cuò)誤牲蜀;我們會(huì)得到和輸入3相同的結(jié)果笆制。的腳的結(jié)論之一就是nil的cdr就是nil。
(nthcdr 4 ’(a b c)) → nil
(nthcdr 5 ’(a b c)) → nil
但如果列表的結(jié)尾不是nil而是一個(gè)元字符的話涣达,輸入的數(shù)字超過(guò)長(zhǎng)度就會(huì)引發(fā)錯(cuò)誤在辆。
(nthcdr 2 ’(a b c . d)) → (c . d)
(nthcdr 3 ’(a b c . d)) → d
(nthcdr 4 ’(a b c . d)) → Error! D is not a list
函數(shù)nth是返回一個(gè)列表的第n個(gè)元素证薇。
(defun nth (n x)
"Returns the Nth element of the list X,
counting from 0."
(car (nthcdr n x)))
既然(NTHCDR 0 x)得到的是列表,(NTH 0 x)得到的是第一個(gè)元素匆篓,以此類推浑度,(NTH 1 x)就是得到第二個(gè)元素。
(nth 0 ’(a b c)) → a
(nth 1 ’(a b c)) → b
(nth 2 ’(a b c)) → c
(nth 3 ’(a b c)) → nil
數(shù)數(shù)字的時(shí)候是從0開始而不是從1開始是Common Lisp一直遵循的慣例鸦概。在我們討論數(shù)組的時(shí)候會(huì)在此接觸到這一點(diǎn)箩张。
6.5.3 last函數(shù)
last函數(shù)返回的是一個(gè)列表的最后一個(gè)內(nèi)存單元,這個(gè)內(nèi)存單元的car是列表的最后一個(gè)元素窗市。根據(jù)定義先慷,這個(gè)單元的cdr是一個(gè)元字符。否則就不是列表的最后一個(gè)單元了咨察。如果列表為空论熙,last會(huì)返回nil。
(last ’(all is forgiven)) → (forgiven)
(last nil) → nil
(last ’(a b c . d)) → (c . d)
(last ’nevermore) → Error! NEVERMORE is not a list.
6.5.4 remove函數(shù)
remove函數(shù)會(huì)刪除列表中的一個(gè)項(xiàng)目摄狱。正常情況下回刪除所有適合條件的元素脓诡,也是有方法刪除其中一部分的(在本章進(jìn)階話題中)。remove返回的結(jié)果是一個(gè)刪除了相關(guān)元素的新的列表二蓝。
(remove ’a ’(b a n a n a)) → (b n n)
(remove 1 ’(3 1 4 1 5 9)) → (3 4 5 9)
remove函數(shù)是一個(gè)非破壞性函數(shù)誉券,當(dāng)從列表中刪除變量的時(shí)候,他不會(huì)改變?nèi)魏巫兞亢蛢?nèi)存單元刊愚。remove得到的結(jié)果是一個(gè)原來(lái)列表的部分的拷貝踊跟。
> (setf spell ’(a b r a c a d a b r a))
(A B R A C A D A B R A)
> (remove ’a spell)
(B R C D B R)
> spell
(A B R A C A D A B R A)
下面的表格會(huì)幫助你記憶,那些函數(shù)拷貝輸入進(jìn)行處理鸥诽,哪些不拷貝商玫。append,reverse牡借,remove返回一個(gè)新的不包括輸入的內(nèi)存單元鏈拳昌,所以他們是要拷貝他們的輸入的。像nthcdr钠龙,nth和last函數(shù)會(huì)返回一個(gè)指針指向輸入的部分組件炬藤。他們不需要拷貝任何對(duì)象,因?yàn)樗麄兯枰膶?duì)象都已經(jīng)存在了碴里。
6.6 列表構(gòu)成集合
集合是對(duì)象的無(wú)序集合沈矿。每一個(gè)對(duì)象只在該集合中出現(xiàn)一次。一些典型的集合就是咬腋,一個(gè)星期的天數(shù)羹膳,整數(shù)的集合(一個(gè)無(wú)限集合),還有住在王家屯晚上吃了麻辣燙的集合根竿。
集合毫無(wú)疑問(wèn)是有列表組成的一個(gè)更加有用的數(shù)據(jù)結(jié)構(gòu)陵像。一些基本的集合操作比如說(shuō)測(cè)試一個(gè)元素是不是在這個(gè)集合當(dāng)中就珠,獲取兩個(gè)集合的并集,交集醒颖,差集(也叫做集合減法)妻怎,還有測(cè)試一個(gè)集合是不是另一個(gè)集合的子集合。這些所有操作的Lisp函數(shù)都會(huì)在下面介紹图贸。
6.6.1 成員(member)
member斷言的作用是檢查一個(gè)元素是不是列表的成員蹂季。如果是列表的成員,那么有這個(gè)元素開始的字列表將會(huì)被返回疏日,不是的話偿洁,會(huì)返回nil。member絕不會(huì)返回T沟优,但是傳統(tǒng)意義上這是一個(gè)斷言涕滋,因?yàn)槿绻匦g(shù)語(yǔ)這個(gè)列表就會(huì)返回非NIL。
> (setf ducks ’(huey dewey louie)) Create a set of ducks.
(HUEY DEWEY LOUIE)
> (member ’huey ducks) Is Huey a duck?
(HUEY DEWEY LOUIE) Non-NIL result: yes.
> (member ’dewey ducks) Is Dewey a duck?
(DEWEY LOUIE) Non-NIL result: yes.
> (member ’louie ducks) Is Louie a duck?
(LOUIE) Non-NIL result: yes.
> (member ’mickey ducks) Is Mickey a duck?
NIL NIL: no
在Lisp的第一個(gè)方言中挠阁,member函數(shù)值返回T或者NIL宾肺。但是熱么決定使得member函數(shù)返回這個(gè)元素開始子列表,成為了一個(gè)非常有用的功能侵俗。這個(gè)擴(kuò)展一如既往的伴隨member作為一個(gè)斷言锨用,因?yàn)闆](méi)有元素的子列表就是false。
這里有一個(gè)例子說(shuō)明為什么member返回子列表是非常有用的隘谣。斷言beforep增拥,在列表l中,x比y先出現(xiàn)的話寻歧,返回真掌栅。
(defun beforep (x y l)
"Returns true if X appears before Y in L"
(member y (member x l)))
> (beforep ’not ’whom
’(ask not for whom the bell tolls))
(WHOM THE BELL TOLLS)
> (beforep ’thee ’tolls ’(it tolls for thee))
NIL
6.6.2 交集(intersection)
intersection函數(shù)是去獲取兩個(gè)列表的交集返回一個(gè)兩個(gè)列表的共有元素的列表。在結(jié)果中码泛,元素出現(xiàn)的順序是未定義的猾封,可能每一個(gè)Lisp實(shí)現(xiàn)都不相同。另外在集合中順序是不重要的噪珊,只有元素本身是重要的晌缘。
> (intersection ’(fred john mary)
’(sue mary fred))
(FRED MARY)
> (intersection ’(a s d f g)
’(v w s r a))
(A S)
> (intersection ’(foo bar baz)
’(xam gorp bletch))
NIL
如果一個(gè)列表中出現(xiàn)了多個(gè)一樣的元素,那就不是一個(gè)真的集合痢站。Common Lisp的集合函數(shù)磷箕,比如intersection和union都可以處理不是集合的列表,但是結(jié)果是不是包括重復(fù)元素就沒(méi)有準(zhǔn)確定義了瑟押,們多事情可能還是要看具體實(shí)現(xiàn)搀捷。
6.6.3 并集(union)
union函數(shù)返回兩個(gè)集合的并集星掰,換言之多望,由兩個(gè)列表的所有元素組成的不重復(fù)列表嫩舟,一個(gè)元素在兩個(gè)列表中都出現(xiàn)了的話,那么在結(jié)果中就只存在一份怀偷。對(duì)集合來(lái)說(shuō)家厌,結(jié)果的元素出現(xiàn)順序并不重要(也沒(méi)有定義)。
> (union ’(finger hand arm)
’(toe finger foot leg))
(FINGER HAND ARM TOE FOOT LEG)
> (union ’(fred john mary)
’(sue mary fred))
(FRED JOHN MARY SUE)
> (union ’(a s d f g)
’(v w s r a))
(A S D F G V W R)
6.6.4 差集(set-difference)
set-difference函數(shù)的功能室差集椎工。返回的是第一個(gè)集合去除和第二個(gè)集合重復(fù)的元素的結(jié)果饭于。再次說(shuō)明,元素出現(xiàn)的順序沒(méi)有定義维蒙。
> (set-difference ’(alpha bravo charlie delta)
’(bravo charlie))
(ALPHA DELTA)
> (set-difference ’(alpha bravo charlie delta)
’(echo alpha foxtrot))
(BRAVO CHARLIE DELTA)
> (set-difference ’(alpha bravo) ’(bravo alpha))
NIL
不像union和intersection掰吕,set-difference函數(shù)不是一個(gè)對(duì)稱函數(shù)。調(diào)換第一個(gè)和第二和輸入會(huì)導(dǎo)致結(jié)果的不同颅痊。
(setf line1 ’(all things in moderation))
(setf line2 ’(moderation in the defense of liberty
is no virtue))
> (set-difference line1 line2)
(ALL THINGS)
> (set-difference line2 line1)
(THE DEFENSE OF LIBERTY IS NO VIRTUE)
6.6.5 substep斷言
如果一個(gè)集合包括了另一個(gè)殖熟,斷言substep返回T,換句話說(shuō)斑响,第一個(gè)集合每一個(gè)元素都是第二個(gè)集合的元素菱属。
(subsetp ’(a i) ’(a e i o u)) → t
(subsetp ’(a x) ’(a e i o u)) → nil
6.7 用集合編程
這里的一個(gè)例子是說(shuō)如何使用集合解決一個(gè)編程問(wèn)題。這個(gè)問(wèn)題是給一個(gè)名字的前面加上一個(gè)稱呼舰罚,吧john doe變成mr john doe或者吧jane doe變成ms jane doe纽门。如果一個(gè)名字已經(jīng)有稱呼了,那么標(biāo)題應(yīng)該被保留营罢,但是如果沒(méi)有的話赏陵,我們來(lái)嘗試根據(jù)第一個(gè)名字來(lái)決定性別然后加上合適的稱呼。
為了解決這樣的問(wèn)題愤钾,我們必須把他分解成小問(wèn)題瘟滨,讓我們開始在這個(gè)首先名字前面有沒(méi)有稱呼這個(gè)小問(wèn)題上。這里有定義一個(gè)函數(shù)來(lái)解決這個(gè)問(wèn)題能颁。
(defun titledp (name)
(member (first name) ’(mr ms miss mrs)))
> (titledp ’(jane doe)) ‘‘Jane’’ is not a title.
NIL
> (titledp ’(ms jane doe)) ‘‘Ms.’’ is in the set of titles.
(MS MISS MRS)
下一個(gè)步驟就是找出這個(gè)單詞是一個(gè)男性名字還是女性名字杂瘸。我們會(huì)使用簡(jiǎn)化一些的樣本來(lái)是的我們的例子保持簡(jiǎn)潔。
(setf male-first-names
’(john kim richard fred george))
(setf female-first-names
’(jane mary wanda barbara kim))
(defun malep (name)
(and (member name male-first-names)
(not (member name female-first-names))))
(defun femalep (name)
(and (member name female-first-names)
(not (member name male-first-names))))
> (malep ’richard) ‘‘Richard’’ is in the set of males.
T
> (malep ’barbara) ‘‘Barbara’’ is not a male name.
NIL
> (femalep ’barbara) ‘‘Barbara’’ is a female name.
T
> (malep ’kim) ‘‘Kim’’ can be either male or female,
NIL so it’s not exclusively male.
現(xiàn)在我們可以寫一個(gè)give-title函數(shù)來(lái)給名字加上稱呼伙菊。當(dāng)然败玉,我們只給沒(méi)有稱呼的名字加上去。如果名字既不屬于男性镜硕,也不屬于女性那就加上mr or ms运翼。
(defun give-title (name)
"Returns a name with an appropriate title on
the front."
(cond ((titledp name) name)
((malep (first name)) (cons ’mr name))
((femalep (first name)) (cons ’ms name))
(t (append ’(mr or ms) name))))
> (give-title ’(miss jane adams)) Already has a title.
(MISS JANE ADAMS)
> (give-title ’(john q public)) Untitled male name.
(MR JOHN Q PUBLIC)
> (give-title ’(barbara smith)) Untitled female name.
(MS BARBARA SMITH)
> (give-title ’(kim johnson)) Untitled, and gender
(MR OR MS KIM JOHNSON) is ambiguous
在這個(gè)例子中比較重要的特性有,1兴枯,將問(wèn)題分解為小函數(shù)血淌。2,逐一測(cè)試寫的每一個(gè)函數(shù),一旦寫好了titlep悠夯,malep和femaiep等斷言癌淮,give-title函數(shù)就很好寫了。
講一個(gè)問(wèn)題分解為子問(wèn)題是一個(gè)很重要的技能沦补。有經(jīng)驗(yàn)的程序員京城可以看到正確的方式如何講一個(gè)問(wèn)題分解成為邏輯上的子問(wèn)題乳蓄,但是初學(xué)者必須同過(guò)聯(lián)系來(lái)建立這種直覺(jué)。
Here are a few more things we can do with these lists of names. The
functions below take no inputs, so their argument list is NIL.
還有一件我們可以做的事情就是關(guān)于這些列表的名字夕膀。下面的函數(shù)沒(méi)有輸入虚倒,也就是參數(shù)列表是NIL。
(defun gender-ambiguous-names ()
(intersection male-names female-names))
(gender-ambiguous-names) → (kim)
(defun uniquely-male-names ()
(set-difference male-names female-names))
(uniquely-male-names) → (john richard fred george)
到現(xiàn)在為止产舞,我們?cè)诒菊乱?jiàn)過(guò)的所有集合都只是由字符或者數(shù)字組成魂奥。操作列表組成的集合也是很簡(jiǎn)單的,但是使用像member易猫,union捧弃,intersection等等函數(shù)的話需要一個(gè)小技巧。詳情請(qǐng)見(jiàn)進(jìn)階話題的討論擦囊。
6.8 列表組成的表格
表格(tables)是另一個(gè)我們可以由列表構(gòu)建的非常有用的數(shù)據(jù)結(jié)構(gòu)违霞。一個(gè)表格,或者聯(lián)合列表瞬场,是一個(gè)列表的列表买鸽。每一個(gè)列表被稱作一個(gè)入口(entry),每一個(gè)入口的car就是入口的key贯被、下面顯示的就是一個(gè)五個(gè)英文單詞和他們的法語(yǔ)翻譯組成的表格眼五。這個(gè)二表哥包括5個(gè)入口,key就是英語(yǔ)單詞彤灶。
(setf words
’((one un)
(two deux)
(three trois)
(four quatre)
(five cinq)))
6.8.1 assoc函數(shù)
The ASSOC function looks up an entry in a table, given its key. Here are
some examples.
assoc函數(shù)在表格中給出一個(gè)key看幼,找到一個(gè)入口,下面是例子:
(assoc ’three words) → (three trois)
(assoc ’four words) → (four quatre)
(assoc ’six words) → nil
assoc尋找五個(gè)之中匹配的入口幌陕,然后返回整個(gè)入口诵姜。如果assoc沒(méi)有找到入口,就返回nil搏熄。
Notice that when ASSOC does find an entry with the given key, the value
it returns is the entire entry. If we want only the French word and not the
entire entry, we can take the second element of the result of ASSOC.
請(qǐng)注意當(dāng)assoc找到對(duì)應(yīng)key的入口的時(shí)候棚唆,返回的值是整個(gè)入口。如果我們只想要法語(yǔ)翻譯的話那么就對(duì)assoc的結(jié)果進(jìn)行進(jìn)一步處理心例,得到入口第二個(gè)元素宵凌。
(defun translate (x)
(second (assoc x words)))
(translate ’one) → un
(translate ’five) → cinq
(translate ’six) → nil
6.8.2 rasscoc
rassoc類似于assoc,但是是著眼于每一個(gè)表格的元素的cdr而不是car止后。(他的名字就是Reverse assoc的縮寫)瞎惫。為了使用符號(hào)作為key的rassoc,表格必須使一個(gè)點(diǎn)對(duì)的列表:
(setf sounds
’((cow . moo)
(pig . oink)
(cat . meow)
(dog . woof)
(bird . tweet)))
(rassoc ’woof sounds) ? (dog . woof)
(assoc ’dog sounds) ? (dog . woof)
assoc和rassoc都是返回他們找到的第一個(gè)符合key的入口,身下的列表就不被搜索了瓜喇。
6.9 表格編程
這里有assoc的另一個(gè)例子逗扒。我們將要?jiǎng)?chuàng)造一個(gè)對(duì)象以及他們描述的表格,這個(gè)表格會(huì)存儲(chǔ)在全局變量things中:
((object1 large green shiny cube)
(object2 small red dull metal cube)
(object3 red small dull plastic cube)
(object4 small dull blue metal cube)
(object5 small shiny red four-sided pyramid)
(object6 large shiny green sphere))
現(xiàn)在我們來(lái)開發(fā)函數(shù)來(lái)告訴我們欠橘,在那個(gè)對(duì)象中有兩個(gè)對(duì)象不同。我們一開始來(lái)寫一個(gè)叫做description的函數(shù)來(lái)導(dǎo)出一個(gè)對(duì)象的描述现恼。
(defun description (x)
(rest (assoc x things)))
(description ’object3) → (red small dull plastic cube)
兩個(gè)對(duì)象之間的區(qū)別是有什么屬性出現(xiàn)在了第一個(gè)對(duì)象的描述中卻沒(méi)有出現(xiàn)在第二個(gè)當(dāng)中肃续,或者在第二個(gè)對(duì)象的描述中出現(xiàn)但第一個(gè)沒(méi)有。用技術(shù)術(shù)語(yǔ)來(lái)說(shuō)就是異或叉袍。這是Common Lisp的內(nèi)建函數(shù)始锚。
(defun differences (x y)
(set-exclusive-or (description x)
(description y)))
(differences ’object2 ’object3) → (metal plastic)
object2是金屬,但是object3是塑料喳逛,所以金屬和塑料的屬性是完全不同的瞧捌。我們可以根據(jù)他們指向的類型的不停來(lái)分辨屬性。這里是一個(gè)點(diǎn)對(duì)列表的表格:
(setf quality-table
’((large . size)
(small . size)
(red . color)
(green . color)
(blue . color)
(shiny . luster)
(dull . luster)
(metal . material)
(plastic . material)
(cube . shape)
(sphere . shape)
(pyramid . shape)
(four-sided . shape)))
我們可以使用這個(gè)表格作為函數(shù)的一部分來(lái)給我們指出給定屬性的物質(zhì):
(defun quality (x)
(cdr (assoc x quality-table)))
(quality ’red) → color
(quality ’large) → size
Using DIFFERENCES and QUALITY, we can write a function to tell us
one quality that is different between a pair of objects.
使用differences和quality润文,我們可以寫一個(gè)函數(shù)來(lái)告訴我們一個(gè)物質(zhì)在兩個(gè)對(duì)象之間有什么不同姐呐。
(defun quality-difference (x y)
(quality (first (differences x y))))
(quality-difference ’object2 ’object3) → material
(quality-difference ’object1 ’object6) → shape
(quality-difference ’object2 ’object4) → color
如果我想要一個(gè)所有物質(zhì)區(qū)別的列表而不只是一個(gè)怎么辦?我們需要一個(gè)方法來(lái)從區(qū)別的列表(RED
BLUE METAL PLASTIC) 到對(duì)應(yīng)物質(zhì)的列表轉(zhuǎn)換典蝌。然后我們也不得不評(píng)價(jià)多個(gè)元素曙砂。第一部分可以依靠sublis完成,這個(gè)函數(shù)我們將在進(jìn)階話題中討論骏掀。
(differences ’object3 ’object4) → (red blue metal plastic)
> (sublis quality-table
(differences ’object3 ’object4))
(COLOR COLOR MATERIAL MATERIAL)
現(xiàn)在我們不得不做的是在結(jié)果中評(píng)價(jià)多個(gè)入口鸠澈。Common Lisp提供了一個(gè)函數(shù)佳作remove-duplicates來(lái)達(dá)到這個(gè)目的。
(defun contrast (x y)
(remove-duplicates
(sublis quality-table (differences x y))))
(contrast ’object3 ’object4) → (color material)
小結(jié)
列表在自己應(yīng)用范圍是一種重要的數(shù)據(jù)結(jié)構(gòu)截驮。但是在lisp中他更重要的地方在于他能夠用來(lái)構(gòu)成其他數(shù)據(jù)結(jié)構(gòu)笑陈,集合和表格。
如所見(jiàn)葵袭,解決任何不簡(jiǎn)單的問(wèn)題的方法及時(shí)將問(wèn)題分解成小問(wèn)題涵妥,一個(gè)個(gè)可控的片段。一些簡(jiǎn)單的函數(shù)可以一個(gè)個(gè)測(cè)試并組合成主要問(wèn)題的解決方案坡锡。
本章涉及函數(shù)
列表函數(shù): APPEND, REVERSE, NTH, NTHCDR, LAST, REMOVE.
集合函數(shù): UNION, INTERSECTION, SET-DIFFERENCE, SETEXCLUSIVE-OR, MEMBER, SUBSETP, REMOVE-DUPLICATES.
表格函數(shù): ASSOC, RASSOC
Lisp Toolkit:sdraw
sdarw是一個(gè)用來(lái)畫列表對(duì)應(yīng)的內(nèi)存單元表達(dá)式的工具妹笆。他并不是Common Lisp標(biāo)準(zhǔn)中的一部分;他被定義在附錄A當(dāng)中娜氏。在完整的移植版本中會(huì)運(yùn)行任何Common Lisp實(shí)現(xiàn)拳缠,使用普通字符來(lái)畫出內(nèi)存單元表示:
還有一個(gè)圖形版本,可以從發(fā)行商那里的軟盤獲得贸弥,是使用圖形來(lái)表示內(nèi)存單元和箭頭的函數(shù)窟坐。那個(gè)圖形表示看上去更好看一些。一個(gè)圖形版本是使用CLX,Common Lisp對(duì)應(yīng)X Window System的接口哲鸳。如果你的電腦不是運(yùn)行Xwindow的話臣疑,會(huì)不能使用這個(gè)版本。但是要是你的Lisp支持其他圖形實(shí)例徙菠,也會(huì)很容易使用sdraw讯沈。
另一個(gè)有用的工具像函數(shù)sdraw-loop,就像一個(gè)read-eval-print循環(huán)一樣工作的畫圖函數(shù)婿奔。sdraw-loop的提示符就是字符串S>缺狠。
第六章進(jìn)階話題
6.10 樹結(jié)構(gòu)(trees)
樹是一個(gè)嵌套列表結(jié)構(gòu)。到現(xiàn)在為止所有的函數(shù)都只是對(duì)于列表的頂層進(jìn)行操作萍摊,他們并不進(jìn)行更深入結(jié)構(gòu)的操作挤茄。Lisp也包括一些新的函數(shù)來(lái)操作整個(gè)列表。其中的兩個(gè)就是subst和sublis冰木。在第八章中我們會(huì)寫很多這樣操作樹結(jié)構(gòu)的函數(shù)穷劈。
6.10.1 subst
subst函數(shù)經(jīng)列表中的一個(gè)項(xiàng)目替換(substitute)為另一個(gè)項(xiàng)目,不論這個(gè)元素出現(xiàn)在列表的何處踊沸。這是一個(gè)三輸入的函數(shù)歇终,輸入的順序是,用x替換z中的y逼龟。
> (subst ’fred ’bill
’(bill jones sent me an itemized
bill for the tires))
(FRED JONES SENT ME AN ITEMIZED
FRED FOR THE TIRES)
如果要被替換的字符沒(méi)有出現(xiàn)在該列表中练湿,則返回原來(lái)的列表。
> (subst ’bill ’fred ’(keep off the grass))
(KEEP OFF THE GRASS)
> (subst ’on ’off ’(keep off the grass))
(KEEP ON THE GRASS)
subst著眼于整個(gè)列表結(jié)構(gòu)审轮,不僅僅是頂層元素肥哎。
> (subst ’the ’a
’((a hatter) (a hare) and (a dormouse)))
((THE HATTER) (THE HARE) AND (THE DORMOUSE))
6.10.2 sublis
sublis類似于subst,除了他可以同時(shí)替換多個(gè)元素疾渣。第一個(gè)輸入是一個(gè)點(diǎn)對(duì)作為入口形成的表格篡诽。第二個(gè)輸入是將要做出替換的列表。
> (sublis ’((roses . violets) (red . blue))
’(roses are red))
(VIOLETS ARE BLUE)
(setf dotted-words
’((one . un)
(two . deux)
(three . trois)
(four . quatre)
(five . cinq)))
> (sublis dotted-words ’(three one four one five))
(TROIS UN QUATRE UN CINQ)
6.11 列表操作的效率
在本章的一開始我們討論了在括號(hào)表達(dá)式中榴捡,列表如何表現(xiàn)出對(duì)稱性杈女,但是實(shí)際上不是這樣的。另一個(gè)方法就是在相對(duì)速度或者制定操作的效率上表現(xiàn)出的非對(duì)稱性吊圾。例如达椰,提取一個(gè)列表的首個(gè)元素很容易,但是提取最后一個(gè)元素卻要很花力氣项乒。當(dāng)要提取起一個(gè)元素的時(shí)候啰劲,我們從指向第一個(gè)內(nèi)存單元的指針開始。函數(shù)first僅僅是提取了那個(gè)單元的car然后返回他檀何。想要找到列表的最后一個(gè)元素要做的工作就要多得多蝇裤。因?yàn)槲ㄒ坏姆椒ň褪且恢闭野≌抑烙龅揭粋€(gè)元素的cdr是一個(gè)元字符的時(shí)候廷支,我們才可以看到那個(gè)單元的car。如果原來(lái)的列表很長(zhǎng)栓辜,那么可能要好一會(huì)才能找到最后一個(gè)單元恋拍。
計(jì)算機(jī)可以循序成百上千的單元來(lái)找到想要的單元,時(shí)間還非常短藕甩,所以一般不需要注意到first和last在速度上的差別施敢。但是如果軟件項(xiàng)目的規(guī)模變得很大,那么這個(gè)細(xì)微差別就會(huì)變得引人注意狭莱。
另一個(gè)會(huì)影響函數(shù)速度的事實(shí)就是有多少切實(shí)的單元存在僵娃。創(chuàng)造一個(gè)新的單元是要耗費(fèi)時(shí)間的,最后也會(huì)占滿計(jì)算機(jī)的內(nèi)存贩毕。在事實(shí)上有一些會(huì)被棄用,但是所占的內(nèi)存卻還在仆嗦。在一些Lisp實(shí)現(xiàn)中辉阶,內(nèi)存會(huì)被沒(méi)有用的單元給占滿,機(jī)器必須暫時(shí)停下來(lái)然后運(yùn)行垃圾回收機(jī)制瘩扼。一個(gè)函數(shù)要處理的單元越多谆甜,垃圾回收機(jī)制運(yùn)行的就越頻繁。我們接下來(lái)來(lái)比較一下這兩個(gè)版本add-to-and函數(shù)的效率:
(defun add-to-end-1 (x y)
(append x (list y)))
(defun add-to-end-2 (x y)
(reverse (cons y (reverse x))))
假設(shè)這些函數(shù)的輸入是一個(gè)n元素的列表集绰。add-to-end1使用append來(lái)拷貝第一個(gè)輸入规辱,然后將第二個(gè)輸入定位到最后。因此他會(huì)創(chuàng)造一個(gè)完整的n+1列表栽燕。add-to-end2開始是將列表反轉(zhuǎn)罕袋,這將會(huì)創(chuàng)造一個(gè)新的列表,然后將第二個(gè)輸入加載這個(gè)列表的前面碍岔,最后反轉(zhuǎn)結(jié)果浴讯,再次創(chuàng)造一個(gè)新的列表。所以add-toend2創(chuàng)造的是n+1+n+1個(gè)單元蔼啦。其中n+1是結(jié)果榆纽,其他的n+1將會(huì)在創(chuàng)造后被拋棄,成為垃圾捏肢,很銘心啊add-to-end1是一個(gè)更加有效率的函數(shù)奈籽,因?yàn)閯?chuàng)造了更少的單元。
6.12 共享結(jié)構(gòu)
當(dāng)兩個(gè)列表共享內(nèi)存單元的時(shí)候鸵赫,就被稱為共享結(jié)構(gòu)衣屏。在顯示器上輸出的列表是不可能體現(xiàn)出共享結(jié)構(gòu)的,因?yàn)槊恳粋€(gè)列表看上去都是一個(gè)新列表辩棒。通過(guò)使用car勾拉,cdr和cons可以構(gòu)造全新的共享列表煮甥。例如我們使得兩個(gè)列表共享一些元素。
> (setf x ’(a b c))
(A B C)
> (setf y (cons ’d (cdr x)))
(D B C)
列表X的值是(A B C)藕赞,Y的值是(D B C)成肘。兩個(gè)列表共享了一部分內(nèi)存單元(B C)。共享結(jié)構(gòu)的建立是因?yàn)閅是由(CDR X)而來(lái)的斧蜕。如果我們直接簡(jiǎn)單定義Y双霍,就不會(huì)有共享結(jié)構(gòu)。
6.13 對(duì)象的相等
在Lisp中批销,符號(hào)是唯一的洒闸,意味著他們?cè)谟?jì)算機(jī)內(nèi)存中只能是一個(gè)具名符號(hào)。每一個(gè)內(nèi)存中的對(duì)象命名位置均芽,我們叫他們地址(address)丘逸。所以在列表(TIME AFTER TIME)中。是沒(méi)有兩個(gè)字符叫做time的掀宋。
列表深纲,從另一個(gè)角度說(shuō)也不是唯一的。我們可以很簡(jiǎn)單的用獨(dú)立的內(nèi)存單元鏈條來(lái)構(gòu)造不同的列表(A B C)劲妙,兩個(gè)列表中的字符是唯一的湃鹊,但是列表本身不是唯一的。這意味著equal函數(shù)是不可以通過(guò)比較兩者的地址來(lái)知道他們是不是相等镣奋,因?yàn)閮蓚€(gè)(A B C)是相等的但是他們是不同的內(nèi)存單元币呵。equal函數(shù)因此事一個(gè)一個(gè)元素的比較兩個(gè)列表。如果兩個(gè)列表的對(duì)應(yīng)元素是相等的侨颈,那么列表本身就被認(rèn)為是相等的余赢。
> (setf x1 (list ’a ’b ’c)) Make a fresh list (A B C).
(A B C)
> (setf x2 (list ’a ’b ’c)) Make another list (A B C).
(A B C)
> (equal x1 x2) The lists are EQUAL.
T
如果我們想知道兩個(gè)指針是不是指向相同的對(duì)象,就必須比較他們的地址哈垢。EQ斷言就是做這個(gè)事情的没佑。列表之間如果有相同的地址,那么他們就是相等的(EQ)温赔,不會(huì)一個(gè)個(gè)比較元素蛤奢。
> (eq x1 x2) The two lists are not EQ.
NIL
> (setf z x1) Now Z points to the same list as X1.
(A B C)
> (eq z x1) So Z and X1 are EQ.
T
> (eq z ’(a b c)) These lists have different addresses.
NIL
> (equal z ’(a b c)) But they have the same elements.
T
EQ函數(shù)要比eqaul快,因?yàn)閑q只是比較一個(gè)個(gè)地址陶贼,equal首先要測(cè)試輸入是不是列表啤贩,如果是必須每一個(gè)元素相對(duì)應(yīng)的比較。由于更有效率拜秧,程序員更喜歡使用eq而不是equal痹屹,除非是想要知道內(nèi)存單元是不是相同。
數(shù)字枉氮,在不同的Lisp系統(tǒng)中也有不同的展現(xiàn)志衍。在一些實(shí)現(xiàn)中暖庄,每一個(gè)數(shù)字都有一個(gè)唯一的地址,然而在另一些實(shí)現(xiàn)中不是楼肪。因此eq不應(yīng)該用來(lái)比較數(shù)字培廓。
eql斷言的行為大體上跟eq相同。他跟eql一樣比較兩個(gè)對(duì)象的地址春叫,除開對(duì)于兩個(gè)相同類型(例如都是整形)數(shù)字的情況下肩钠。他會(huì)比較兩個(gè)數(shù)字的值。不同類型的數(shù)字在eql中不相等暂殖,即使是值相等价匠。
(eql ’foo ’foo) → t
(eql 3 3) → t
(eql 3 3.0) → nil Different types.
eql是Common Lisp中的“默認(rèn)”比較斷言。例如member函數(shù)和assoc函數(shù)都是默認(rèn)使用eql作為相等的比較函數(shù)呛每,除非指定使用其他的踩窖。
對(duì)于比較兩個(gè)完全不同類型的數(shù)字,現(xiàn)在也有另一個(gè)比較斷言叫做=晨横。這個(gè)斷言是最常用的比較連個(gè)數(shù)字的方法洋腮。給出數(shù)字外的其他輸入會(huì)報(bào)錯(cuò)。
(= 3 3.0) → t
(= ’foo ’foo) → Error! FOO is not a number
最后颓遏,equalp斷言是相似于equal的斷言徐矩,但是在一些情況下更加寬容一些滞时。一個(gè)例子就是他會(huì)忽視字符串比較的大小寫叁幢。
(equal "foo bar" "Foo BAR") → nil
(equalp "foo bar" "Foo BAR") → t
初學(xué)者經(jīng)常會(huì)對(duì)Common Lisp中的大量相等比較感到困惑,我推薦的是忘記這些所有特殊的函數(shù)坪稽。只是記住兩條建議曼玩。第一,使用equal窒百,他就是你想要的黍判。第二內(nèi)建函數(shù)memeber和assoc等等在內(nèi)部出于效率都是使用eql作為默認(rèn)的。這意味著他們頸部會(huì)正確比較列表篙梢,除非你告訴他們用不同的相等斷言顷帖。下一小節(jié)我們解釋如何做到這一點(diǎn):
概述一下:
- eq是最快的相等測(cè)試,他比較的是地址渤滞。專家一般是用這個(gè)來(lái)快速比較符號(hào)贬墩,測(cè)試他們是不是在內(nèi)存單元中是同一個(gè)物理單元。eq不應(yīng)該被用來(lái)比較數(shù)字妄呕。
- eql類似于eq陶舞,除了他可以安全的比較相同類型的數(shù)字,例如兩個(gè)整形或者兩個(gè)浮點(diǎn)數(shù)绪励。在common Lisp中他是默認(rèn)的相等測(cè)試斷言肿孵。
- equal是一個(gè)初學(xué)者使用的斷言唠粥。他逐個(gè)比較列表的元素,除此之外和eql的行為相同停做。
- equalp是更加寬容的斷言晤愧,和equal比較的話,他會(huì)忽視字符大小寫的諸如此類的問(wèn)題雅宾。
- =是一個(gè)比較數(shù)字最有效的方式养涮,也是比較不同類型數(shù)字的唯一方式,比如3和3.0.他只接受數(shù)字眉抬。
6.14 關(guān)鍵字參數(shù)(keyword argument)
很多Common Lisp函數(shù)都支持在列表中附加的贯吓,可選的參數(shù),叫做關(guān)鍵字參數(shù)蜀变。例如悄谐,remove函數(shù)可以接受一個(gè)可選的參數(shù)叫做,:count來(lái)告訴函數(shù)库北,到底刪除多少個(gè)對(duì)象爬舰。
(setf text ’(b a n a n a - p a n d a))
> (remove ’a text) Remove all As.
(B N N - P N D)
> (remove ’a text :count 3) Remove 3 As.
(B N N - P A N D A)
remove也接受一個(gè):from-end關(guān)鍵字。如果他的值不是nil寒瓦,那么remove就會(huì)從列表的最后開始操作情屹,而不是從開始。所以為了刪除列表中的最后兩個(gè)A杂腰,我們可以這樣寫:
> (remove ’a text :count 2 :from-end t)
(B A N A N A - P N D)
一個(gè)關(guān)鍵字是一個(gè)特殊類型的字符垃你,他的名字總是在一個(gè)冒號(hào)的后面。字符count和:count不是相同的喂很,他們是不同的對(duì)象惜颇,在eq中也是不同的。關(guān)鍵字總是求值為自身的少辣,所以他們不需要被引用凌摄,嘗試改變一個(gè)關(guān)鍵字的值會(huì)引發(fā)錯(cuò)誤。斷言keywordp漓帅,如果輸入是關(guān)鍵字的話就會(huì)返回T锨亏。
:count → :count
(symbolp :count) → t
(equal :count ’count) → nil
(keywordp ’count) → nil
(keywordp :count) → t
另一個(gè)可以接受關(guān)鍵字參數(shù)的函數(shù)是member,正常來(lái)說(shuō)忙干,member使用eql來(lái)測(cè)試一個(gè)項(xiàng)目是不是出現(xiàn)在了一個(gè)集合里器予。eql對(duì)符號(hào)和數(shù)字都是有效的。但是假設(shè)我們的集合包括了列表怎么辦豪直?在那種情況下我們必須使用equal來(lái)做相等判斷劣摇,否則其他的member將不會(huì)找到我們要找的項(xiàng)目。
(setf cards
’((3 clubs) (5 diamonds) (ace spades)))
(member ’(5 diamonds) cards) → nil
(second cards) → (five diamonds)
(eql (second cards) ’(5 diamonds)) → nil
(equal (second cards) ’(5 diamonds)) → t
關(guān)鍵字:test可以再member函數(shù)中使用來(lái)指定一個(gè)相等判斷的函數(shù)弓乙。我們?cè)谔囟ǖ囊?hào)后面寫上函數(shù)名作為一種輸入末融。
> (member ’(5 diamonds) cards :test #’equal)
((5 DIAMONDS) (ACE SPADES))
所有的列表函數(shù)钧惧,包括相等測(cè)試等等都接受一個(gè):test關(guān)鍵字參數(shù)。remove函數(shù)是另一個(gè)例子勾习,我們不能從cards中刪除(5 diamonds)除非我們告訴remove使用equal作為相等判斷浓瞪。
> (remove ’(5 diamonds) cards)
((3 CLUBS) (5 DIAMONDS) (ACE SPADES))
> (remove ’(5 diamonds) cards :test #’equal)
((3 CLUBS) (ACE SPADES))
另一個(gè)接受:test關(guān)鍵字的函數(shù)是union,intersection和set-difference巧婶,assoc乾颁,rassoc,subst和sublis艺栈。為了找出函數(shù)接受哪一個(gè)關(guān)鍵字英岭,可以使用在線文檔。給函數(shù)輸入他不支持的關(guān)鍵字的話會(huì)報(bào)錯(cuò)湿右。
> (remove ’(ace spades) cards :reason ’bad-luck)
Error! :REASON is an invalid keyword argument
to REMOVE.
進(jìn)階話題涉及函數(shù)
樹函數(shù): SUBST, SUBLIS.
附加的相等函數(shù): EQ, EQL, EQUALP, =.
關(guān)鍵字?jǐn)嘌? KEYWORDP