Lisp語言:列表(List)

終於開始討論列表了,列表是Lisp的精華之一,也是學習Lisp的難點之一。

列表的精彩之處在於,它不僅僅是Lisp中的一種數據結構,它也是Lisp語言的構成部分,Lisp語言中的所有語句都是一個列表。
反觀我們常見的編程語言,語言中的語句有特定的語法,而這些語法只有編譯器可以理解,用編程語言自己去解釋自己幾乎就成了一個不可能完成的任務。
舉個例子,看看下面的java語句:
[plain] view plaincopy
  1. int i = j + 10 ;  
這句代碼給java編譯器去解釋是很簡單的,i的值會等於j加10.
不過,如果給你下面名叫myString這個字符串:
[plain] view plaincopy
  1. String myString = "int i = j + 10 ;"  
你能否通過java代碼解析myString,並準確理解這行代碼的意義?
對於一般程序員來講這個任務幾乎是不可能完成的。當然,對於大牛來講是可以的,不過是用java語言寫一個java編譯器而已。

而Lisp程序員就幸運多了,不用成爲大牛也可以通過Lisp語言解析Lisp語句,因爲Lisp語句本身就是一個列表,如下面的代碼:
[plain] view plaincopy
  1. (setf i (+ j 10))  

以上語句就是一個列表,通過Lisp語言的列表操作函數可以輕易地解析出其中的所有元素。當然,單純地解析語句還不能算是一個編譯器,要做一個Lisp編譯器還需要很多工作。


更爲幸運的是,Lisp語言中提供了eval函數,把一個列表當參數傳遞給eval函數,eval函數會解析並運行該參數,就像是在Lisp語言中提供了一個現成的Lisp編譯器。
就像下面的代碼:
[plain] view plaincopy
  1. (defun eval-test ()  
  2.         (setf j 5)  
  3.         (eval '(setf i (+ j 10)))  
  4.         (format *query-io* "i is: ~A ~%" i))  
其中(setf i (+ j 10))被輕鬆地編譯執行了。
在這裏編譯期和運行期的界限被模糊了,我們有時會分不清我們是在寫程序還是在編譯程序。這也就是爲什麼Lisp被稱之爲“可以編寫語言的語言”。

有關“可以編寫語言的語言”,更多的內容我們在討論宏(macro)的時候深入討論,在這裏只是開始討論列表而已。



如果我們在學習Lisp中的列表時把列表當作一個單純的數據結構會比較簡單一些,所以我們就先從數據結構的角度先了解一下列表。
其實,就是把列表當作一個簡單的數據結構,我們也無法在一篇文章中完全介紹它,所以我們分幾篇文章來討論列表。

因爲常規語言中沒有對應的數據結構,我們首先需要解釋一下什麼是Lisp語言中列表。
Lisp語言中的列表可以簡單地理解爲圓括號包圍的一堆數據,數據之間用空格隔開,像下面這個就是一個列表:
[plain] view plaincopy
  1. (a b c)  
以上列表包含三個元素,分別是a ,b  和c。
下面還有一個列表的樣例:
[plain] view plaincopy
  1. (abc 10 efg hi)  
以上列表包含四個元素,分別是abc, 10 , efg和hi。
列表中單個的元素被稱作原子(atom)

列表的元素不一定需要是原子,可以是另一個列表,如下面這樣:
[plain] view plaincopy
  1. (a b (abc 10 efg hi) c)  
以上列表有四個元素,分別是a , b  , (abc 10 efg hi)和 c。



如果我們希望創建一個列表,賦予變量my-list,下面這樣是不行的:
[plain] view plaincopy
  1. (setf my-list (a b c))  
原因是Lisp語言解釋器把(a b c)當作一行語句,嘗試運算它,運算時會把a當作函數名,而b和c當作是參數。


正確的寫法應該是:

[plain] view plaincopy
  1. (setf my-list '(a b c))  
其中的'符號表示後面的列表不需要進行計算。


另外一種寫法是:

[plain] view plaincopy
  1. (setf my-list (quote (a b c)))  
第一中寫法中的'(a b c)其實就是(quote (a b c))的簡寫。


再還有一種寫法是:

[plain] view plaincopy
  1. (setf my-list (list 'a 'b 'c))  
其中list函數用於創建列表,將元素'a, 'b 和'c組合成一個列表。
以上三個語句可以認爲是等價的。

創建了列表就需要訪問它,常規的訪問某個位置的函數是nth,形式如下:
[plain] view plaincopy
  1. (nth 0 my-list)  
以上函數返回my-list中的第一個元素,可以發現nth函數同樣是以0爲下標起始點的,也就是說下面的函數會返回my-list的第8個元素:
[plain] view plaincopy
  1. (nth 7 my-list)  




如果希望對列表進行遍歷,可以使用loop函數,形式如下: 
[plain] view plaincopy
  1.          (loop for x in my-list do  
  2.                 (format *query-io* "~a::" x))  
以上函數對my-list進行遍歷,逐個輸出元素的值。


此外,因爲列表的特殊性,Lisp語言還提供了car和cdr兩個函數對列表進行操作。
car函數返回列表的第一個元素,如:
[plain] view plaincopy
  1. (car my-list)  
以上函數返回my-list中的第一個元素,以原子的方式返回。
結合上面的定義,(car my-list)的返回值是A.

而cdr函數返回列表第一個元素以後的所有元素,返回時是一個列表。如下面的函數:
[plain] view plaincopy
  1. (cdr my-list)  
結合以上的定義,該函數返回(B C)


更爲有趣的是cdr函數中的d字符可以重複,如cddr,或者是cdddr。
其中cddr返回第二個元素以後的所有元素,而cdddr返回第三個元素以後的所有元素。d的重複次數最多爲四次,cddddr這樣是合法的,而cdddddr會報錯。

Lisp中還可以通過(cadr my-list)這樣的形式表示(car (cdr my-list))
這樣返回的是my-list中第二個元素,以原子的形式返回。
同樣,其中的d字符可以,最多重複三次,如caddr, cadddr這樣。


如果希望往一個列表添加元素,可以使用append函數,形式如下:
[plain] view plaincopy
  1. (append my-list '(d))  
結合上面對my-list的定義,my-list的值是(A B C),所以以上函數返回(A B C D)


注意函數(append my-list '(d))並不會改變列表my-list的值,它只是返回了一個新的列表,新的列表中包含了my-list中的值和新添加的值。

如果希望操作my-list,讓my-list多一個元素D,需要將代碼寫成這樣:
[plain] view plaincopy
  1. (setf my-list (append my-list '(d)))  



以上就是lisp中列表的一些基本操作。
不過列表的操作遠不止這些,更多的列表操作我們在以後的文章中討論。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章