Common Lisp:符號計算簡單介紹(第二章上)

第二章 列表(LISTS)

2.1 列表是最豐富多彩的數據類型

Lisp這個名稱是list Processor的縮寫。雖然Lisp已經發展成熟很多年,但是列表依然是核心數據類型。列表的重要在于,他幾乎可以表現為任何形式:集合,表格,圖表,甚至英語句子。列表也可以用來表現函數,但是這個話題我們留到后續章節介紹。

2.2 列表看上去是什么樣子的?

每一個列表都有兩個形式: 打印形式和內部的存儲形式。打印形式因為簡潔易懂的形式更容易被人理解。內部形式則是列表在計算機內存中真實存在的樣子。我們將會用一種圖形表達方法來表示內部的存儲形式。
在打印形式中,一個列表由一連串項目組成,以圓括號包裹形成。這些項目被統稱為列表的元素。下面有一些用括號表達式來標示的例子:

(RED GREEN BLUE)
(AARDVARK)
(2 3 5 7 11 13 17)
(3 FRENCH HENS 2 TURTLE DOVES 1 PARTRIDGE 1 PEAR TREE)

列表的內部形式并不包括圓括號,在內存中,列表被組織稱為一連串的內存單位(Cons Cells),下同中的矩形框所表示。而這些內存單位是由指針(內存地址)連接在一起,下圖中的箭頭表示。每一個內存單位有兩個指針,其中一個指向列表的元素,比如下圖中的RED,另一個指向里標的下一個內存單位。當我們談論到“列表可能包括字符串或者數字作為元素。”的時候我們實際上是指內存單位可能包括指向字符串或者數字的指針,當然也包括指向其他單元的指針。列表(RED GREEN BLUE)在計算機內部的表現如下圖:



看到這張圖的最右邊,你或許注意到,列表鏈的最后是由NIL來作為結束,這是Lisp當中的慣例。這也許違背了某些環境的規定,但是在大部分時候列表總是由NIL來結尾。在括號表達式中,在列表最后的NIL往往省略處理,這也是慣例。
在計算機內部,實際上每一個內存單位都是一小段內存,然后人為的分成了兩部分,只要足夠達到儲存數據指針的大小,事實上的的真實數據存儲在其他地方,單元中存儲的只是數據的指針(內存地址)。一般來說,大部分計算機的指針式4個字節,所以每一個內存單位是8個字節。

練習題

2.1用盒裝圖畫出列表(TO BE OR NOT TO BE)在計算機內部的存儲圖示。

2.3 單元素列表

一個字符串和一個只有一個元素的列表不是一樣的。下圖所示的列表(AARDVARK),使用一個內存單位來體現。一種一個單元的指針指向字符串AARDVARK,另一個指針指向NIL。所以顯而易見列表(AARDVARK)和字符串AARDVARK是完全不同的對象。前者的是一個內存單元,他的指針指向后者。


2.4嵌套列表

一個列表可能包含其他列表作為自己的元素。給出下面三個列表:

(BLUE SKY)
(GREEN GRASS)
(BROWN EARTH)

我們可以用另一對圓括號來把這三個列表聚合成一個列表。結果如下:(這里提示一下注意層次的重要,這是單個列表組成的列表,不是六個字符串組成的列表)。

((BLUE SKY) (GREEN GRASS) (BROWN EARTH))

我們愿意的話可以把上例中的水平表示轉換成垂直表示。空格和縮進并不影響列表的意思,只要圓括號和元素沒有改變的話。上例中的列表也能寫成這樣:

((BLUE SKY)
(GREEN GRASS)
(BROWN EARTH))

第一個元素仍然是(BLUE SKY)。用盒子圖形表示的話就會是下圖的樣子,列表的三個元素就是第一層次的三個內存單元。每一個元素都是一個含有兩個字符串的列表,每一個頂層單元指向一個低層次的雙單元列表。



任何書寫出來的括號表達式在計算機內部都有一個內存單元結構對應(如果括號匹配不錯)。加入括號匹配錯誤。比如這個列表“‘‘(RED (GREEN BLUE”,計算機不能正確生成一個相對應的內存結構,那就會返回一個錯誤消息。

練習

2.2下列列表哪些是格式正確的?也就是說,哪一個是括號疲憊正確的?

(A B (C)
((A) (B))
A B )(C D)
(A (B (C))
(A (B (C)))
(((A) (B)) (C))

2.3 畫出列表(PLEASE (BE MY) VALENTINE)的盒裝圖。
2.4 下列內存結構的括號表達式是什么?


2.5 列表的長度

列表的長度就是列表中元素的個數,例如,列表(HI MOM)的長度就是2。那有列表組成的列表長度高如何計算?一個括號表達式寫就的列表,他的元素就是在第一層次的項目。例如列表 (A (B C) D)中的元素就是A,列表(B C)和D。字符B,C都僅僅是元素列表(B C)的組成部分。
需要注意的是,在計算機內部是不使用括號的。從計算機的角度來看,列表(A (B C) D)包括三個元素,因為在內部的存儲的形式就只有三個內存單元,如圖所示:



由此可見,一個列表的長度和他元素的復雜成都是沒有關系的。一下列表都確實只有三個元素,雖然一些列表的元素本身是列表。三個元素已經有下劃線表示出來。



原始函數LENGTH就是用來計算列表長度的函數。如果給定的輸入并不是列表,而是字符串或者數字,那就會報錯。

練習題

2.5下列每一個列表分別含有多少個元素?

長度 列表
(OPEN THE POD BAY DOORS HAL)
((OPEN) (THE POD BAY DOORS) HAL)
((1 2 3) (4 5 6) (7 8 9) (10 11 12))
((ONE) FOR ALL (AND (TWO (FOR ME))))
((Q SPADES) (7 HEARTS) (6 CLUBS) (5 DIAMONDS) (2 DIAMONDS))
((PENNSYLVANIA (THE KEYSTONE STATE)) (NEW-JERSEY (THE GARDEN STATE)) (MASSACHUSETTS (THE BAY STATE)) (FLORIDA (THE SUNSHINE STATE)) (NEW-YORK (THE EMPIRE STATE)) (INDIANA (THE HOOSIER STATE)))

2.6 空列表

沒有元素的列表叫做空列表。沒有內存單位,寫作一對空的圓括號。
()
在計算機內部,空列表是用NIL來表示。這里是一個難點:字符串NIL就是空列表,那又為什么把NIL作為一個列表鏈的結尾呢?
既然空列表和NIL是完全相同的,那么可以很自然地把NIL和()互相置換。比如列表(A NIL B)也可以寫成(A () B)。這兩者在使用上沒有任何區別因為在計算機內部是完全相同的兩個對象。
空列表的長度是0,雖然NIL是一個字符串,但是因為NIL也是一個列表,所以作為LENGTH的輸入時合法的。NIL也是唯一一個既是列表又是字符串的對象。


練習題

2.6既然NIL和()是可以互相替換的,那么請將左側一列和右側一列一一對應起來。(請注意括號的層次)


2.7 列表之間的相等

如果相對應的每一個元素都是相等的,那么兩個列表就被認為是相等的。看一下下面的兩個列表(A (B C) D)和(A B (C D))。



這兩個列表由同樣的元素數量(都是三個)。辦事他們不是相等的。第一個列表的第二個元素是(B C),但是第二個列表的第二個元素是B。而且這兩個列表都不等于(A B C D),因為他是四個元素的。加入兩個列表有著不同的元素數量,則是絕不可能相等的。

2.8 FIRST,SECOND,THIRD和REST

Lisp提供了從列表中提取元素的原始函數。函數FIRST,SECOND,和THIRD分別返回輸入的列表的第一第二和第三個元素。




如果輸入不是列表,就會報錯。



REST函數是FIRST函數的補集,REST函數會返回除了第一個元素的所有元素。

使用FIRST函數和重復使用REST函數就可以構造出我們自己的SECOND,THIRD,FOURTH等等。

假如MY-SECOND的輸入是(PENGUINS LOVE ITALIAN ICES),那REST函數將會輸出列表(LOVE ITALIAN ICES),,這個列表的首個首個元素就是LOVE。


練習題

2.7當給出輸入(HONK IF YOU LIKE GEESE)的時候,MY-SECOND函數的內部是如何運行的?
2.8怎樣使用一個FIRST函數和兩個REST函數寫一個MY-THIRD函數?
2.9怎樣使用SECOND函數寫一個MY-THIRD函數?

2.9函數操作指針

當我們說一個對象(比如列表或者字符串)成為一個函數的輸入的時候,其實這是不準確的。在計算機內部,每一個操作都是圍繞指針來進行的,所以真實的輸入不是對象本身,而是指向對象的指針。與之相同的,函數的返回值實際上也是一個指針。
假設(THE BIG BOPPER)作為REST的輸入,REST函數實際上接受的是第一個內存單位的一個指針。這個指針在下圖中由波浪線表示,用波浪線來表示是因為這個指針不能明確的表示出來,換言之,這個指針并不存在于內存單元的內部,他存儲在計算機的其他地方,計算機科學家們稱作在一個寄存器或者在堆棧中。這些細節方面的事情我們現在還不用考慮。



REST函數返回的結果是第二個內存單元的指針,也就是列表(BIG BOPPER)的第一個元素。那這個指針式從何而來?REST函數所做的事情就是把第一個元素的右半部分的指針提取出來,并作為結果返回。REST函數返回的是同一個內存單元鏈條的指針,并沒有新的列表被REST函數創造出來。所有的操作僅僅是提取并返回一個函數。



NOTE:上圖中戰士的指向字符串THE的指針的意思是為了強調REST函數返回的結果是同一個列表的一部分。但是指向字符串THE的內存單元并不是REST 函數返回結果的一部分。

2.10 CAR和CDR

到現在為止已經明白,每一個內存單元的一半都儲存著一個指針,并且指向某個對象。每一半都有一個名字,左半部分的名字是CAR,后半部分叫做CDR。這些名字是早期計算的歷史遺留。當Lisp在IBM704上第一次運行的時候,704太原始以至于還沒用晶體管還在使用真空管。他的每一個寄存器都被分割成一些獨立的組件,每兩個組成地址部分和減量部分。那時候,CAR就是寄存器地址部分內容(Contents of Address portion of Register)的縮寫,CDR就是寄存器減量部分內容(Contents of Decrement portion of Register)的縮寫,雖然這些術語并沒有應用在現代計算機的硬件上,但是Common Lisp部分由于歷史原因任然使用CAR和CDR術語來指代內存單元。部分也是由于長格式名稱的組成,比如CADR和CDDAR。
CAR和CDR除了作為命名之外,也是內建Lisp函數的名字,作用是分別返回存單元的左半部分和右半部分指針。再看一下列表(THE BIG
BOPPER),當作為函數CAR的輸入的時候,函數接受的并不是列表本身,而是第一個內存單元的指針。



輸入CAR函數的內存單元,提取內存單元指針的CAR部分。所以CAR函數返回的是字符串THE的指針。那同樣列表作為輸入的時候CDR函數會輸出什么呢?



輸入CDR函數的內存單元,提取內存單元指針的CDR部分。所以CAR函數返回的是列表(BIG BOPPER)的指針。從此例中可以看出,CAR和FIRST,CDR和REST的功能是相同的。Lisp程序員經常如此表述:FIRST返回列表的CAR,REST返回列表的CDR。

2.10.1 一個單元素列表的CDR

之前看到的列表(AARDVARK)和字符串AARDVARK不一樣、列表是這樣:



既然一個長度為1的列表在計算機內部表現為一個內存單元,那么這個長度為1的CDR就是一個長度為0的列表,NIL。


列表((PHONE HOME))只有一個元素。請記住一個列表的元素只是最外層括號之內的對象,這些項目是由第一層次的內存單元指針指向決定的。這個列表的圖示是這樣的:



既然CAR和CDR是從列表的內存單元中提取出特定的指針。那么列表((PHONE HOME))的CAR部分就是(PHONE HOME)。CDR部分就是NIL。


練習題

2.10 畫出列表(((PHONE HOME)))的內存形式圖,他有幾層括號,CAR,CDR是什么?
2.11 畫出列表(A (TOLL) ((CALL)))的內存形式圖。

2.10.2 CAR和CDR的組合

觀察這樣一個列表(FEE FIE FOE FUM),第一個元素是FEE。提取第二個元素的操作是將列表輸入FIRST函數,之后再輸入REST,或者以我們新的術語,先CDR之后CAR。



如果把函數名從左往右讀出來,會先讀出CDR然后讀出CAR。既然CAR函數的輸入就是CDR的輸出,用英語說就是列表的CDR的CAR。在Lisp中CDR的CAR會被縮寫成為CADR。



如果把A和D調換一下位置會發生什么?函數CADR是提取列表的CAR的CDR,列表(FEE FIE
FOE FUM)的CAR就是FEE,假如我們想要獲得這個列表的CDR,那就會返回錯誤。

CDDAR函數返回列表的第三個元素。(如果對新的縮略詞由困惑的話,請參考下面的發音指導)這些函數的名字體系那了這個函數的工作方式:CADDR就是提取列表的CDR的CDR的CAR。



怎樣理解CADDR是如何工作,你可以蔥油往左讀出A和D的數目。列表(FEE FIE FOE FUM)作為輸入,首先首先是CDR,輸出就是(FIE FOE FUM)。然后再一次提取CDR,給出結果(FOE FUM)。最后再提取CAR,就得出了FOE。
也有另一個角度來看待CADDR,首先是CDDR也就是提取CDR的CDR,或者說是REST的REST。列表(FEE FIE FOE FUM)的CDDR是(FOE FUM)。然后在提取CAR就是FOE。CADDR其實就是CDDR的CAR。
Common Lisp提供了很多有關CAR,CDR內建函數,包括了四位AD的所有組合情況。CAADDR是內建的,但是CAADDAR就不是內建函數了。Lisp也提供了從FIRST到TENTH的內建函數定義。
練習題

2.12 什么樣的C...R組合會返回列表的第四個元素?怎樣讀這樣一個函數?

2.10.3 嵌套函數的CAR和CDR

CAR和CDR也可以像使用在普通列表上一樣應用在嵌套列表上。讓我們看看如何獲取列表((BLUE
CUBE) (RED PYRAMID))內部的組件。




列表的CAR就是(BLUE CUBE)。為了提取BLUE
,我們必須提取CAR的CAR.這個CAAR函數的作用就是這個。



也有另一個角度來看待這個過程。嵌套列表的第一個元素是(BLUE CUBE),所以CUBE就是列表的FIRST的SECOND。也就是CAR的CADR,等同于CADAR。
現在我們來嘗試獲取字符串RED,RED是列表的SECOND的FIRST,也就是CADR的CAR。把兩個名字融合到一起就是CAADR。具體的過程如下:
練習題

2.13 畫出列表(((FUN)) (IN THE) (SUN))的到每一個字符串的具體過程,利用上圖表示的倒三角形式。
2.14 使用列表((A B) (C D) (E F))作為輸入,填空

Function Result
CAR (A B)
CDDR
CADR
CDAR
B
CDDAR
A
CDADDR
F

2.16 CAAR以列表(FRED NIL)作為輸入時如何處理的?

2.10.4 關于NIL的CAR和CDR

關于NIL有一個有趣的現實:NIL的CAR和CDR是被定義為NIL的。關于這一點為什么真么做可能會有一些讓人疑惑。在Lisp的一些早期方言版本中,把NIL作為CAR和CDR的輸入實際上會返回錯誤。但是經驗顯示,把NIL的CAR和CDR定義為NIL有一個很有用的結果,在后續章節中我們會有所介紹。



既然函數FIRST,SECOND,THIRD等等內部都是有CAR和CDR組成所定義的,單一需要解析的一個列表比較短的時候,那么返回的結果就是NIL。比如返回列表(DING ALING)的第三個元素,但是這個列表沒有第三個元素,結果就是NIL。


練習題

2.17 完成下列計算過程



最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容