跳轉至內容

newLISP/列表簡介

來自 Wikibooks,開放世界的開放書籍

列表在 newLISP 中無處不在 - LISP 代表 **列表處理** - 所以有許多有用的函式來處理列表並不奇怪。 將它們全部組織成一個合乎邏輯的描述性敘述非常困難,但這裡是對大多數函式的介紹。

newLISP 的一個好處是,許多在列表上工作的函式也適用於字串,所以你將在下一章中再次遇到許多這些函式,在那裡它們將應用於字串。

構建列表

[編輯 | 編輯原始碼]

你可以直接構建一個列表並將其分配給一個符號。 引用列表以阻止它立即被求值。

(set 'vowels '("a" "e" "i" "o" "u"))
;-> ("a" "e" "i" "o" "u") ; an unevaluated list

列表通常由其他函式為你建立,但你也可以使用以下函式構建自己的列表。

  • **list** 從表示式中建立一個新列表。
  • **append** 將列表粘合在一起以形成一個新列表。
  • **cons** 將元素新增到列表的開頭或建立一個列表。
  • **push** 在列表中插入一個新成員。
  • **dup** 複製一個元素。

list、append 和 cons

[編輯 | 編輯原始碼]

使用 **list** 從表示式序列構建列表。

(set 'rhyme (list 1 2 "buckle my shoe" 
    '(3 4) "knock" "on" "the" "door"))
; rhyme is now a list with 8 items"
;-> (1 2 "buckle my shoe" '(3 4) "knock" "on" "the" "door")

請注意,(3 4) 元素本身是一個列表,它巢狀在主列表中。

**cons** 接受兩個表示式,並且可以完成兩個工作:將第一個元素插入現有列表的開頭,或建立一個新的包含兩個元素的列表。 在這兩種情況下,它都會返回一個新列表。 newLISP 會根據第二個元素是否為列表自動選擇要執行的操作。

(cons 1 2)                           ; makes a new list
;-> (1 2)

(cons 1 '(2 3))                      ; inserts an element at the start
;-> (1 2 3)

要將兩個或多個列表粘合在一起,請使用 **append**。

(set 'odd '(1 3 5 7) 'even '(2 4 6 8))
(append odd even)
;-> (1 3 5 7 2 4 6 8)

請注意,當你加入兩個列表時,**list** 和 **append** 之間的區別。

(set 'a '(a b c) 'b '(1 2 3))

(list a b)
;-> ((a b c) (1 2 3))                   ; list makes a list of lists

(append a b)
;-> (a b c 1 2 3)                       ; append makes a list

**list** 在建立新列表時保留源列表,而 **append** 使用每個源列表的元素建立一個新列表。

要記住這一點:List 保留源列表的 List 性質,但 aPPend 會將元素挑選出來並將它們打包在一起。

**append** 也可以將一堆字串組裝成一個新的字串。

push:將專案推入列表

[編輯 | 編輯原始碼]

**push** 是一個功能強大的命令,你可以使用它來建立一個新列表或將元素插入現有列表的任何位置。 將元素推入列表的開頭會將所有元素向右移動一位,而將元素推入列表的末尾只會將它附加並建立一個新的最後一個元素。 你也可以將元素插入列表中間的任何位置。

儘管它具有構造性,但它在技術上是一個破壞性函式,因為它會永久更改目標列表,因此請謹慎使用它。 它返回插入的元素的值,而不是整個列表。

(set 'vowels '("e" "i" "o" "u"))
(push (char 97) vowels)
; returns "a"
; vowels is now ("a" "e" "i" "o" "u")

當你引用列表中元素的位置時,你使用的是從零開始的編號,這正是你作為經驗豐富的程式設計師所期望的。

index numbering of list elements

如果你沒有指定位置或索引,**push** 會將新元素推入開頭。 使用第三個表示式來指定新元素的位置或索引。 -1 表示列表的最後一個元素,1 表示從開頭(從 0 開始)計算的列表的第二個元素,依此類推。

(set 'vowels '("a" "e" "i" "o"))
(push "u" vowels -1)
;-> "u"
; vowels is now ("a" "e" "i" "o" "u")

(set 'evens '(2 6 10))
(push 8 evens -2)                       ; goes before the 10
(push 4 evens 1)                        ; goes after the 2

; evens is now (2 4 6 8 10)

如果你提供的符號作為列表不存在,**push** 會很有用地為你建立它,因此你不需要先宣告它。

(for (c 1 10)
 (push c number-list -1)                ; doesn't fail first time!
 (println number-list))
(1)
(1 2)
(1 2 3)
(1 2 3 4)
(1 2 3 4 5)
(1 2 3 4 5 6)
(1 2 3 4 5 6 7)
(1 2 3 4 5 6 7 8)
(1 2 3 4 5 6 7 8 9)
(1 2 3 4 5 6 7 8 9 10)


順便說一下,還有很多其他方法可以生成無序數字列表。 你也可以進行多次隨機交換,例如

(set 'l (sequence 0 99))
(dotimes (n 100)
  (swap (l (rand 100)) (l (rand 100)))))

儘管使用 **randomize** 會更容易

(randomize (sequence 1 99))
;-> (54 38 91 18 76 71 19 30 ...

(這是 newLISP 的優點之一 - 更優雅的解決方案只需要重新編寫!)

**push** 有一個相反的函式 **pop**,它會破壞性地從列表中刪除元素,並返回刪除的元素。 我們將在後面遇到 **pop** 和其他列表手術函式。 請參閱 列表手術

這兩個函式,就像 newLISP 中的許多其他函式一樣,對字串和列表都有效。 請參閱 push 和 pop 也適用於字串

dup:構建重複元素的列表

[編輯 | 編輯原始碼]

一個名為 **dup** 的有用函式可以讓你透過重複元素指定次數來快速構建列表。

(dup 1 6)             ; duplicate 1 six times
;-> (1 1 1 1 1 1)

(dup '(1 2 3) 6)
;-> ((1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3) (1 2 3))

(dup x 6)
;-> (x x x x x x)

有一個技巧可以讓 **dup** 返回一個字串列表。 因為 **dup** 也可以用來將字串複製成一個更長的字串,所以你在列表的末尾提供一個額外的 **true** 值,newLISP 就會建立一個字串列表,而不是一個字串的字串。

(dup "x" 6)           ; a string of strings
;-> "xxxxxx"

(dup "x" 6 true)      ; a list of strings
;-> ("x" "x" "x" "x" "x" "x")

使用整個列表

[編輯 | 編輯原始碼]

一旦你擁有了一個列表,你就可以開始對它進行操作。 首先,讓我們看看對列表作為一個整體進行操作的函式。 之後,我會看看讓你可以執行列表手術的函式 - 對單個列表元素的操作。

使用和處理列表

[編輯 | 編輯原始碼]

dolist 用於遍歷列表中的每個專案。

(set 'vowels '("a" "e" "i" "o" "u"))
(dolist (v vowels)
  (println (apply upper-case (list v))))
A
E
I
O
U


在這個例子中,apply 期望一個函式和一個列表,並將該列表的元素作為引數傳遞給函式。因此,它重複地將upper-case 函式應用於迴圈變數在v 中的值。由於upper-case 用於字串,而apply 期望一個列表,因此我必須使用list 函式將每次迭代中v 的當前值(一個字串)轉換為列表。

更好的方法是使用map

(map upper-case '("a" "e" "i" "o" "u"))
;-> ("A" "E" "I" "O" "U")

map 將指定的函式(本例中為upper-case)依次應用於列表中的每個專案。map 的優勢在於它在一趟遍歷中既遍歷了列表,又將函式應用於列表中的每個專案。結果也是一個列表,這可能對後續處理更有用。

關於dolistapply 的更多內容可以在其他地方找到(參見 遍歷列表,以及 Apply 和 map:將函式應用於列表)。

reverse

[edit | edit source]

reverse 的作用正如你所預期的那樣,它反轉列表。它是一個破壞性函式,會永久改變列表。

(reverse '("A" "E" "I" "O" "U"))
;-> ("U" "O" "I" "E" "A")

sort 和 randomize

[edit | edit source]

在某種程度上,randomizesort 是互補的,儘管sort 會改變原始列表,而randomize 會返回原始列表的無序副本。sort 將列表中的元素按升序排列,並按型別和值進行組織。

以下是一個例子:建立一個字母列表和一個數字列表,將它們粘在一起,然後對結果進行隨機排列,最後再排序。

(for (c (char "a") (char "z"))
 (push (char c) alphabet -1))

(for (i 1 26) 
 (push i numbers -1))

(set 'data (append alphabet numbers))

;-> ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p"
; "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" 1 2 3 4 5 6 7 8 9 10 11 12
; 13 14 15 16 17 18 19 20 21 22 23 24 25 26)

(randomize data)

;-> ("l" "r" "f" "k" 17 10 "u" "e" 6 "j" 11 15 "s" 2 22 "d" "q" "b" 
; "m" 19 3 5 23 "v" "c" "w" 24 13 21 "a" 4 20 "i" "p" "n" "y" 14 "g" 
; 25 1 8 18 12 "o" "x" "t" 7 16 "z" 9 "h" 26)

(sort data)

;-> (1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
; 25 26 "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o"
; "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")

比較隨機化之前和排序之後的datasort 命令按資料型別和值對列表進行排序:整數在字串之前,字串在列表之前,依此類推。

預設排序方法是<,它將值排列成每個值都小於下一個值。

要更改排序方法,可以提供 newLISP 的內建比較函式之一,例如>。當比較函式對每對相鄰元素都為真時,認為相鄰物件是正確排序的。

(for (c (char "a") (char "z")) 
  (push (char c) alphabet -1)) 

alphabet
;-> ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" 
; "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")

(sort alphabet >)
;-> ("z" "y" "x" "w" "v" "u" "t" "s" "r" "q" "p" "o" "n" 
; "m" "l" "k" "j" "i" "h" "g" "f" "e" "d" "c" "b" "a")

可以提供自定義排序函式。這是一個接受兩個引數的函式,如果它們處於正確的順序(即第一個應該在第二個之前),則返回真,否則返回假。例如,假設你想對檔名列表進行排序,使最短的名稱出現在最前面。定義一個函式,如果第一個引數比第二個引數短,則返回真,然後使用該自定義排序函式呼叫sort

(define (shorter? a b)        ; two arguments, a and b 
 (< (length a) (length b)))

(sort (directory) shorter?)
;->
("." ".." "var" "usr" "tmp" "etc" "dev" "bin" "sbin" "mach" ".vol" 
"Users" "cores" "System" "Volumes" "private" "Network" "Library" 
"mach.sym" ".Trashes" "Developer" "automount" ".DS_Store" 
"Desktop DF" "Desktop DB" "mach_kernel" "Applications" "System Folder" ...)


經驗豐富的 newLISP 使用者通常會編寫一個無名函式,並直接將其提供給sort 命令。

(sort (directory) (fn (a b) (< (length a) (length b))))

這完成了相同的工作,但節省了大約 25 個字元。可以使用fnlambda 來定義內聯或匿名函式。

unique

[edit | edit source]

unique 返回一個列表的副本,其中刪除了所有重複項。

(set 'data '( 1 1 2 2 2 2 2 2 2 3 2 4 4 4 4))
(unique data)
;-> (1 2 3 4)

還有一些用於比較列表的有用函式。參見 使用兩個或更多列表

flat 對處理巢狀列表很有用,因為它可以展示巢狀列表的結構,而不會出現複雜的層次結構。

(set 'data '(0 (0 1 2) 1 (0 1) 0 1 (0 1 2) ((0 1) 0))) 
(length data)
;-> 8
(length (flat data))
;-> 15
(flat data)
;-> (0 0 1 2 1 0 1 0 1 0 1 2 0 1 0)

幸運的是,flat 是非破壞性的,因此可以使用它,而不用擔心會丟失巢狀列表的結構。

data
;-> (0 (0 1 2) 1 (0 1) 0 1 (0 1 2) ((0 1) 0)) ; still nested

transpose

[edit | edit source]

transpose 用於處理矩陣(一種特殊的列表型別:參見 矩陣)。它對普通的巢狀列表也有一些作用。如果你將列表的列表想象成一個表格,它會為你翻轉行和列。

(set 'a-list 
 '(("a" 1) 
   ("b" 2) 
   ("c" 3)))

(transpose a-list)

;->
(("a" "b" "c")
 ( 1 2 3))

(set 'table 
'((A1 B1 C1 D1 E1 F1 G1 H1)
  (A2 B2 C2 D2 E2 F2 G2 H2)
  (A3 B3 C3 D3 E3 F3 G3 H3)))

(transpose table)

;->
((A1 A2 A3)
 (B1 B2 B3)
 (C1 C2 C3)
 (D1 D2 D3)
 (E1 E2 E3)
 (F1 F2 F3) 
 (G1 G2 G3) 
 (H1 H2 H3))

下面是 newLISP 技巧的一段例子。

(set 'table '((A 1) (B 2) (C 3) (D 4) (E 5)))
;-> ((A 1) (B 2) (C 3) (D 4) (E 5))

(set 'table (transpose (rotate (transpose table))))
;-> ((1 A) (2 B) (3 C) (4 D) (5 E))

每個子列表都被反轉了。當然,你可以這樣做。

(set 'table (map (fn (i) (rotate i)) table))

這更短,但速度稍慢。

explode

[edit | edit source]

explode 函式可以讓你將列表“爆炸”。

(explode (sequence 1 10))
;-> ((1) (2) (3) (4) (5) (6) (7) (8) (9) (10))

你也可以指定片段的大小。

(explode (sequence 1 10) 2)
;-> ((1 2) (3 4) (5 6) (7 8) (9 10))

(explode (sequence 1 10) 3)
;-> ((1 2 3) (4 5 6) (7 8 9) (10))

(explode (sequence 1 10) 4)
;-> ((1 2 3 4) (5 6 7 8) (9 10))

列表分析:測試和搜尋

[edit | edit source]

通常你不知道列表裡有什麼,你需要一些取證工具來了解更多資訊。newLISP 提供了不錯的選擇。

我們已經瞭解過length,它可以找到列表中的元素數量。

starts-withends-with 函式測試列表的開頭和結尾。

(for (c (char "a") (char "z")) 
 (push (char c) alphabet -1))

;-> alphabet is ("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l"
; "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")

(starts-with alphabet "a")             ; list starts with item "a"?
;-> true

(starts-with (join alphabet) "abc")    ; convert to string and test
;-> true

(ends-with alphabet "z")               ; testing the list version
;-> true

這些函式對字串也同樣適用(而且它們接受正則表示式)。參見 測試和比較字串

contains 怎麼樣?實際上,newLISP 中沒有一個單獨的函式可以完成這項工作。相反,你有findmatchmemberreffilterindexcount 等。使用哪一個取決於你想對問題“這個列表是否包含這個專案?”得到什麼樣的答案,以及列表是巢狀列表還是平面列表。

如果你想要一個簡單的答案,並且只需進行快速的一級搜尋,可以使用find。參見 find

如果你也想要該專案和列表的剩餘部分,可以使用member。參見 member

如果你想要第一個出現的索引號,即使列表包含巢狀列表,也可以使用ref。參見 ref 和 ref-all

如果你想要一個包含所有與搜尋元素匹配的元素的新列表,可以使用find-all。參見 find-all

如果你想知道列表是否包含某種元素模式,可以使用match。參見 匹配列表中的模式

你可以使用filtercleanindex 函式找到所有滿足函式(內建函式或自定義函式)的列表項。參見 過濾列表:filter、clean 和 index

existsfor-all 函式檢查列表中的元素,以檢視它們是否透過測試。

如果你想在列表中找到元素只是為了將它們更改為其他東西,那麼就不要先搜尋它們,直接使用replace 就可以了。參見 替換資訊:replace。你還可以使用set-ref 函式查詢和替換列表元素。參見 查詢和替換匹配的元素

如果你想知道列表中某個專案的出現次數,可以使用count。參見 使用兩個或更多列表

讓我們看一些這些函式的例子。

find 在列表中查詢表示式,並返回一個整數或nil。該整數是列表中搜索項第一次出現的索引。find 可能返回 0 - 如果列表以該項開頭,則其索引號為 0,但這並不成問題 - 你可以在if 測試中使用此函式,因為 0 評估為真。

(set 'sign-of-four 
 (parse (read-file "/Users/me/Sherlock-Holmes/sign-of-four.txt")
 {\W} 0))

(if  (find "Moriarty" sign-of-four)        ; Moriarty anywhere?
  (println "Moriarty is mentioned")
  (println "No mention of Moriarty"))
No mention of Moriarty 


(if (find "Lestrade" sign-of-four)
  (println "Lestrade is mentioned") 
  (println "No mention of Lestrade"))
Lestrade is mentioned.
(find "Watson" sign-of-four)
;-> 477

這裡我解析了亞瑟·柯南·道爾爵士的《四簽名》(可以在古騰堡計劃下載),並測試了生成的字串列表是否包含各種姓名。返回的整數是該字串元素在列表中第一次出現的索引。

find 允許你使用正則表示式,因此你可以找到列表中與字串模式匹配的任何字串元素。

(set 'loc (find "(tea|cocaine|morphine|tobacco)" sign-of-four 0)) 
(if loc
  (println "The drug " (sign-of-four loc) " is mentioned.")
  (println "No trace of drugs"))
The drug cocaine is mentioned.

這裡我正在尋找福爾摩斯波西米亞生活方式中任何化學放縱的痕跡:"(tea|cocaine|morphine|tobacco)" 表示茶、可卡因、嗎啡或菸草中的任何一種。

這種形式的find 允許你在列表的字串元素中查詢正則表示式模式。在探索字串時,你將再次遇到正則表示式。參見 正則表示式

(set 'word-list '("being" "believe" "ceiling" "conceit" "conceive" 
"deceive" "financier" "foreign" "neither" "receive" "science" 
"sufficient" "their" "vein" "weird"))

(find {(c)(ie)(?# i before e except after c...)} word-list 0)
;-> 6                                   ; the first one is "financier"

這裡我們正在尋找單詞列表中任何與我們的模式匹配的字串元素(一個c 後面跟著ie,這是舊的、不準確的拼寫規則i before e except after c)。

這裡的正則表示式模式(用大括號括起來,大括號是字串分隔符,其作用與引號類似)是 (c) 後面跟著 (ie)。然後是一個註釋,以(?# 開頭。正則表示式中的註釋在事情變得難以理解時很有用,因為它們經常會變得難以理解。

find 還可以接受比較函式。參見 搜尋列表

find 僅查詢列表中的第一個匹配項。要查詢所有匹配項,您可以重複使用 find 直到它返回 nil。每次迴圈中,單詞列表都會變短,並且找到的元素將新增到另一個列表的末尾。

(set 'word-list '("scientist" "being" "believe" "ceiling" "conceit" 
"conceive" "deceive" "financier" "foreign" "neither" "receive" "science" 
"sufficient" "their" "vein" "weird"))

(while (set 'temp 
 (find {(c)(ie)(?# i before e except after c...)} word-list 0))
   (push (word-list temp) results -1)
   (set 'word-list ((+ temp 1) word-list)))

results
;-> ("scientist" "financier" "science" "sufficient")

但在這種情況下,使用 filter 會更容易。

(filter (fn (w) (find {(c)(ie)} w 0)) word-list)
;-> ("scientist" "financier" "science" "sufficient")

- 請參見 過濾列表:filter、clean 和 index

或者,您可以使用 ref-all(請參見 ref 和 ref-all)來獲取索引列表。

如果您沒有使用正則表示式,可以使用 count,它在給定兩個列表的情況下,會遍歷第二個列表並計算第一個列表中的每個項出現的次數。讓我們看看主要角色的名字被提到了多少次。

(count '("Sherlock" "Holmes" "Watson" "Lestrade" "Moriarty" "Moran")
 sign-of-four)
;-> (34 135 24 1 0 0)

count 生成的結果列表顯示了第一個列表中的每個元素在第二個列表中出現的次數,因此在這個故事中,Sherlock 被提及了 34 次,Holmes 被提及了 135 次,Watson 被提及了 24 次,而可憐的警長 Lestrade 僅被提及了一次。

值得注意的是,find 只會簡單地檢查列表。例如,如果列表包含巢狀列表,您應該使用 ref 而不是 find,因為 ref 會檢視子列表內部。

(set 'maze 
 '((1 2)
   (1 2 3)
   (1 2 3 4)))

(find 4 maze)
;-> nil                           ; didn't look inside the lists

(ref 4 maze)
;-> (2 3)                         ; element 3 of element 2

member 函式返回源列表的剩餘部分,而不是索引號或計數。

(set 's (sequence 1 100 7))             ; 7 times table?
;-> (1 8 15 22 29 36 43 50 57 64 71 78 85 92 99)

(member 78 s)
;-> (78 85 92 99)

匹配列表中的模式

[編輯 | 編輯原始碼]

有一個功能強大且複雜的函式稱為 match,它用於查詢列表中的模式。它接受萬用字元 *、 ? 和 +,您使用它們來定義元素的模式。+ 表示一個或多個元素,* 表示零個或多個元素, ? 表示一個元素。例如,假設您想在一個包含 0 到 9 之間的隨機數字的列表中查詢模式。首先,生成一個包含 10000 個隨機數的列表作為源資料。

(dotimes (c 10000) (push (rand 10) data))
;-> (7 9 3 8 0 2 4 8 3 ...)

接下來,您決定要查詢以下模式:

 1 2 3

列表中的某個位置,即任何內容後面跟著 1,然後是 2,然後是 3,然後是任何內容。像這樣呼叫 match

(match '(* 1 2 3 *) data)

看起來很奇怪,但這只是一個列表中的模式規範,後面跟著源資料。列表模式:

(* 1 2 3 *)

表示任何原子或表示式的序列(或無),後面跟著一個 1,然後是一個 2,然後是一個 3,後面跟著任意數量的原子或表示式或無。此 match 函式返回的答案是另一個列表,包含兩個子列表,一個對應於第一個 *,另一個對應於第二個 *。

((7 9 3 8  . . .  0 4 5)  (7 2 4 1 . . . 3 5 5 5))

您正在尋找的模式第一次出現在這些列表之間的間隙(實際上,它在列表中後面出現了六次)。match 也可以處理巢狀列表。

要查詢所有模式的出現,而不僅僅是第一個,您可以在 while 迴圈中使用 match。例如,要查詢並刪除所有緊隨其後的 0,請在列表的新版本上重複 match,直到它停止返回非 nil 值。

(set 'number-list '(2 4 0 0 4 5 4 0 3 6 2 3 0 0 2 0 0 3 3 4 2 0 0 2))

(while (set 'temp-list (match '(* 0 0 *) number-list))
  (println temp-list)
  (set 'number-list (apply append temp-list)))
((2 4) (4 5 4 0 3 6 2 3 0 0 2 0 0 3 3 4 2 0 0 2))
((2 4 4 5 4 0 3 6 2 3) (2 0 0 3 3 4 2 0 0 2))
((2 4 4 5 4 0 3 6 2 3 2) (3 3 4 2 0 0 2))
((2 4 4 5 4 0 3 6 2 3 2 3 3 4 2) (2))
> number-list
;-> (2 4 4 5 4 0 3 6 2 3 2 3 3 4 2 2)


您不必先找到元素,然後再替換它們:只需使用 replace,它會在一個操作中執行查詢和替換。您也可以使用 match 作為搜尋列表的比較函式。請參見 替換資訊:replace搜尋列表

find-all 是一個功能強大的函式,具有多種不同的形式,適用於搜尋列表、關聯列表和字串。對於列表搜尋,您需要提供四個引數:搜尋鍵、列表、操作表示式和函式,它是您要用於匹配搜尋鍵的比較函式。

(set 'food '("bread" "cheese" "onion" "pickle" "lettuce"))
(find-all "onion" food (print $0 { }) >)
;-> bread cheese lettuce

這裡,find-all 正在列表 food 中搜索字串“onion”。它使用 > 函式作為比較函式,因此它將找到所有大於“onion”的任何內容。對於字串,‘大於’意味著在預設的 ASCII 排序順序中出現在後面,因此“cheese” 大於 “bread” 但小於 “onion”。請注意,與其他允許您提供比較函式的函式(即 findrefref-allreplace 在用於列表時、set-refset-ref-allsort)不同,比較函式 **必須** 提供。使用 < 函式,結果是“onion” 小於的列表。

(find-all "onion" food (print $0 { }) <)
;-> pickle

ref 和 ref-all

[編輯 | 編輯原始碼]

ref 函式返回列表中元素的第一個出現的索引。它特別適合用於巢狀列表,因為與 find 不同,它會檢視所有子列表內部,並返回元素第一次出現的 **地址**。例如,假設您已使用 newLISP 的內建 XML 解析器將一個 XML 檔案(例如您的 iTunes 庫)轉換為一個大型巢狀列表。

(xml-type-tags nil nil nil nil)         ; controls XML parsing
(set 'itunes-data 
 (xml-parse
   (read-file "/Users/me/Music/iTunes/iTunes Music Library.xml") 
   (+ 1 2 4 8 16)))

現在,您可以在資料中查詢任何表示式,該資料以普通 newLISP 列表的形式存在。

(ref "Brian Eno" itunes-data)

返回的列表將是該字串在列表中第一次出現的地址。

(0 2 14 528 6 1)

- 這是一組索引號,它們共同定義了一種 **地址**。此示例表示:在列表元素 0 中,查詢子列表元素 2,然後查詢該子列表的子列表元素 14,依此類推,深入到高度巢狀的基於 XML 的資料結構中。請參見 使用 XML

ref-all 執行類似的工作,並返回地址列表。

(ref-all "Brian Eno" itunes-data)
;-> ((0 2 14 528 6 1) (0 2 16 3186 6 1) (0 2 16 3226 6 1))

這些函式也可以接受比較函式。請參見 搜尋列表

當您在巢狀列表中搜索內容時,請使用這些函式。如果您想在找到它時替換它,請使用 set-refset-ref-all 函式。請參見 查詢和替換匹配元素

過濾列表:filter、clean 和 index

[編輯 | 編輯原始碼]

在列表中查詢內容的另一種方法是過濾列表。就像淘金一樣,您可以建立過濾器,只保留您想要的內容,將不需要的內容沖洗掉。

filterindex 具有相同的語法,但 filter 返回列表元素,而 index 返回想要元素的索引號(索引),而不是列表元素本身。(這些函式不適用於巢狀列表。)

過濾函式 filtercleanindex 使用另一個函式來測試元素:根據元素是否透過測試,它會出現在結果列表中。您可以使用內建函式,也可以定義自己的函式。通常,用於測試並返回 true 或 false 的 newLISP 函式(有時稱為謂詞函式)的名稱以問號結尾。

NaN? array? atom? context? directory? empty? file? float? global? integer? lambda? legal? list? macro? nil? null? number? primitive? protected? quote? string? symbol? true? zero?

因此,例如,一種簡單的方法是在列表中查詢整數(並刪除浮點數)是使用 integer? 函式與 filter 結合使用。只有整數才能透過此過濾器。

(set 'data '(0 1 2 3 4.01 5 6 7 8 9.1 10))
(filter integer? data)
;-> (0 1 2 3 5 6 7 8 10)

filter 有一個補充函式稱為 clean,它會刪除滿足測試條件的元素。

(set 'data '(0 1 2 3 4.01 5 6 7 8 9.1 10))
(clean integer? data)
;-> (4.01 9.1)

clean 想象成去除汙垢 - 它會去除任何透過測試的東西。將 filter 想象成淘金,保留透過測試的東西。

下一個過濾器會找到柯南·道爾的短篇小說《空屋》中包含字母 pp 的所有單詞。過濾器是一個 lambda 表示式(一個沒有名字的臨時函式),如果元素不包含 pp 則返回 nil。列表是由 parse 生成的字串元素列表,它根據模式將字串分解成更小的字串列表。

(set 'empty-house-text 
 (parse 
   (read-file "/Users/me/Sherlock-Holmes/the-empty-house.txt")
   {,\s*|\s+} 0))

(filter (fn (s) (find "pp" s)) empty-house-text)

;->
("suppressed" "supply" "disappearance" "appealed" "appealed"
"supplemented" "appeared" "opposite" "Apparently" "Suppose"
"disappear" "happy" "appears" "gripped" "reappearance."
"gripped" "opposite" "slipped" "disappeared" "slipped"
"slipped" "unhappy" "appealed" "opportunities." "stopped"
"stepped" "opposite" "dropped" "appeared" "tapped"
"approached" "suppressed" "appeared" "snapped" "dropped"
"stepped" "dropped" "supposition" "opportunity" "appear"
"happy" "deal-topped" "slipper" "supplied" "appealing"
"appear")

您也可以使用 filterclean 來整理列表,然後使用它們 - 例如,刪除由 parse 操作生成的空字串。

您將在什麼時候使用 index 而不是 filterclean?嗯,當您之後要透過索引號而不是其值訪問列表元素時,請使用 index:我們將在下一節中介紹用於透過索引選擇列表項的函式。例如,ref 僅找到第一個出現的索引,而您可以使用 index 返回元素的每個出現的索引號。

如果您有一個謂詞函式用於查詢字母 c 後面是 ie 的字串,您可以使用該函式來搜尋匹配字串的列表。

(set 'word-list '("agencies" "being" "believe" "ceiling"
"conceit" "conceive" "deceive" "financier" "foreign"
"neither" "receive" "science" "sufficient" "their" "vein"
"weird"))

(define (i-before-e-after-c? wd)        ; a predicate function
 (find {(c)(ie)(?# i before e after c...)} wd 0))

(index i-before-e-after-c? word-list)
;-> (0 7 11 12)                         
; agencies, financier, science, sufficient

請記住,列表可以包含巢狀列表,並且某些函式不會檢視子列表內部。

(set 'maze 
 '((1 2.1)
   (1 2 3)
   (1 2 3 4)))

(filter integer? maze)
;-> ()                                  ; I was sure it had integers...

(filter list? maze)                     
;-> ((1 2.1) (1 2 3) (1 2 3 4))         ; ah yes, they're sublists!

(filter integer? (flat maze))           
;-> (1 1 2 3 1 2 3 4)                   ; one way to do it...

測試列表

[編輯 | 編輯原始碼]

existsfor-all 函式檢查列表中的元素,以檢視它們是否透過測試。

exists 返回列表中第一個透過測試的元素,如果它們都沒有透過測試,則返回 nil

(exists string? '(1 2 3 4 5 6 "hello" 7))
;-> "hello"

(exists string? '(1 2 3 4 5 6 7))
;-> nil

for-all 返回 true 或 nil。如果每個列表元素都透過測試,則返回 true。

(for-all number? '(1 2 3 4 5 6 7))
;-> true

(for-all number? '("zero" 2 3 4 5 6 7))
;-> nil

搜尋列表

[編輯 | 編輯原始碼]

find, ref, ref-allreplace 用於在列表中查詢專案。通常,使用這些函式來查詢與目標匹配的專案。但是,相等性只是預設測試:所有這些函式都可以接受可選的比較函式,該函式用於代替相等性測試。這意味著可以查詢滿足任何測試的列表元素。

以下示例使用 < 比較函式。find 查詢第一個與 n 比較結果為真的元素,即第一個小於 n 的元素。當值為 1002 時,滿足測試的第一個元素是 1003,即列表中的第 3 個元素,因此返回的值為 3。

(set 's (sequence 1000 1020))
;-> (1000 1001 1002 1003 1004 1005 1006 1007 1008 100
; 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020)

(set 'n 1002)
; find the first something that n is less than:
(find n s <)

;-> 3, the index of 1003 in s

可以編寫自己的比較函式

(set 'a-list 
   '("elephant" "antelope" "giraffe" "dog" "cat" "lion" "shark" ))

(define (longer? x y)
  (> (length x) (length y)))

(find "tiger" a-list longer?)
;-> 3 ; "tiger" is longer than element 3, "dog"

longer? 函式在第一個引數比第二個引數長時返回 true。因此,find 使用此函式作為比較函式,查詢列表中第一個使比較結果為真的元素。因為 tigerdog 長,所以函式返回 3,即 dog 在列表中的索引。

可以將匿名(或 lambda)函式作為 find 函式的一部分提供,而不是編寫單獨的函式

(find "tiger" a-list (fn (x y) (> (length x) (length y))))

如果希望程式碼可讀,可能會將更長或更復雜的比較器移到它們自己的單獨(且有文件記錄的)函式中。

還可以將比較函式與 refref-allreplace 一起使用。

比較函式可以是任何接受兩個值並返回 true 或 false 的函式。例如,以下函式在 y 大於 6 且小於 x 時返回 true。因此,搜尋的是 data 列表中比目標數字(本例中為 15)小,但又大於 6 的元素。

(set 'data '(31 23 -63 53 8 -6 -16 71 -124 29))

(define (my-func x y)
  (and (> x y) (> y 6)))

(find 15 data my-func)
;-> 4 ; there's an 8 at index location 4

摘要:比較和對比

[編輯 | 編輯原始碼]

為了總結這些 contains 函式,以下是它們的實際應用

(set 'data 
   '("this" "is" "a" "list" "of" "strings" "not" "of" "integers"))

(find "of" data)                        ; equality is default test
;-> 4                                   ; index of first occurrence

(ref "of" data)                         ; where is "of"?
;-> (4)                                 ; returns a list of indexes

(ref-all "of" data)                     
;-> ((4) (7))                           ; list of address lists

(filter (fn (x) (= "of" x)) data)       ; keep every of
;-> ("of" "of")                         

(index (fn (x) (= "of" x)) data)        ; indexes of the of's
;-> (4 7)                               

(match (* "of" * "of" *) data)          ; three lists between the of's
;-> (("this" "is" "a" "list") ("strings" "not") ("integers"))

(member "of" data)                      ; and the rest
;-> ("of" "strings" "not" "of" "integers")

(count (list "of") data)                ; remember to use two lists
;-> (2)                                 ; returns list of counts

從列表中選擇專案

[編輯 | 編輯原始碼]

有各種函式用於獲取儲存在列表中的資訊

  • first 獲取第一個元素
  • rest 獲取除第一個元素之外的所有元素
  • last 返回最後一個元素
  • nth 獲取第 n 個元素
  • select 按索引選擇特定元素
  • slice 提取子列表

firstrest 函式是傳統 carcdr LISP 函式的更合理的名稱,這些名稱基於舊計算機硬體暫存器的名稱。

選取元素:nth、select 和 slice

[編輯 | 編輯原始碼]

nth 獲取列表中的第 n 個元素

(set 'phrase '("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog"))

(nth 1 phrase)
;-> "quick"

nth 還可以檢視巢狀列表內部,因為它接受多個索引號

(set 'zoo
 '(("ape" 3)
   ("bat" 47)
   ("lion" 4)))
(nth '(2 1) zoo)                         ; item 2, then subitem 1
;-> 4

如果要從列表中選取一組元素,會發現 select 很實用。它可以用兩種不同的形式使用。第一種形式允許提供一系列鬆散的索引號

(set 'phrase '("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog"))

(select phrase 0 -2 3 4 -4 6 1 -1)
;-> ("the" "lazy" "fox" "jumped" "over" "the" "quick" "dog")

正數透過從開頭向前計數來選擇元素,負數透過從結尾向後計數來選擇元素

   0      1       2      3       4      5      6      7     8
("the" "quick" "brown" "fox" "jumped" "over" "the" "lazy" "dog")
  -9     -8      -7     -6      -5     -4     -3     -2    -1


還可以向 select 提供索引號列表。例如,可以使用 rand 函式生成 0 到 8 之間的 20 個隨機數列表,然後使用此列表從 phrase 中隨機選擇元素

(select phrase (rand 9 20))
;-> ("jumped" "lazy" "over" "brown" "jumped" "dog" "the" "dog" "dog"
; "quick" "the" "dog" "the" "dog" "the" "brown" "lazy" "lazy" "lazy" "quick")

注意重複項。如果改為編寫以下內容

(randomize phrase)

將不會有重複項:(randomize phrase) 會對元素進行洗牌,但不會重複它們。

slice 允許提取列表的部分。向其提供列表,後跟一個或兩個數字。第一個數字是起始位置。如果省略第二個數字,則返回列表的其餘部分。如果第二個數字為正數,則表示要返回的元素數量。

(slice (explode "schwarzwalderkirschtorte") 7)
;-> ("w" "a" "l" "d" "e" "r" "k" "i" "r" "s" "c" "h" "t" "o" "r" "t" "e")

(slice (explode "schwarzwalderkirschtorte") 7 6)
;-> ("w" "a" "l" "d" "e" "r")

如果為負數,則第二個數字指定從列表末尾向後計數的切片另一端的元素,-1 表示最後一個元素

(slice (explode "schwarzwalderkirschtorte") 19 -1)
;-> ("t" "o" "r" "t")

切刀會到達指定元素的範圍,但不包括該元素。

隱式定址

[編輯 | 編輯原始碼]

newLISP 提供了一種更快、更高效的方式來選擇和切片列表。可以使用索引號和列表組合,而不是使用函式。這種技術稱為隱式定址。

使用隱式定址選擇元素

[編輯 | 編輯原始碼]

作為使用 nth 的替代方法,將列表的符號和索引號放在一個列表中,如下所示

(set 'r '("the" "cat" "sat" "on" "the" "mat"))
(r 1)                                   ; element index 1 of r
;-> "cat"

(nth 1 r)                               ; the equivalent using nth
;-> "cat"

(r 0)
;-> "the"

(r -1)
;-> "mat"

如果有一個巢狀列表,可以提供一系列索引號來識別層次結構中的列表

(set 'zoo 
 '(("ape" 3) 
   ("bat" 47) 
   ("lion" 4)))                            ; three sublists in a list

(zoo 2 1)
;-> 4
(nth '(2 1) zoo)                           ; the equivalent using nth
;-> 4

其中 '(2 1) 首先查詢元素 2,("lion" 4),然後查詢該子列表中的元素 1(第二個元素)。

使用隱式定址選擇切片

[編輯 | 編輯原始碼]

還可以使用隱式定址來獲取列表的切片。這次,在一個列表中,在列表的符號之前放置一個或兩個數字來定義切片

(set 'alphabet '("a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" 
"l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"))

(13 alphabet)                 ; start at 13, get the rest
;-> ("n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")

(slice alphabet 13)           ; equivalent using slice
;-> ("n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z")

(3 7 alphabet)                ; start at 3, get 7 elements
;-> ("d" "e" "f" "g" "h" "i" "j")

(slice alphabet 3 7)          ; equivalent using slice
;-> ("d" "e" "f" "g" "h" "i" "j")

之前解析了 iTunes XML 庫

(xml-type-tags nil nil nil nil)
(silent 
 (set 'itunes-data
   (xml-parse 
    (read-file 
      "/Users/me/Music/iTunes/iTunes Music Library.xml")
   (+ 1 2 4 8 16))))

讓我們使用隱式定址技術訪問生成的 XML 結構內部

(set 'eno (ref "Brian Eno" itunes-data))
;-> (0 2 14 528 6 1)                    ; address of Brian Eno

(0 4 eno)                               ; implicit slice
;-> (0 2 14 528)

(itunes-data (0 4 eno))
;->
(dict 
 (key "Track ID") 
 (int "305") 
 (key "Name") 
 (string "An Ending (Ascent)") 
 (key "Artist") 
 (string "Brian Eno") ; this was (0 2 14 528 6 1)
 (key "Album") 
 (string "Ambient Journeys") 
 (key "Genre") 
 (string "ambient, new age, electronica") 
 (key "Kind") 
 (string "Apple Lossless audio file") 
 (key "Size") 
 (int "21858166") 
; ... 
 )

如何記住兩種隱式定址之間的區別?sLice 數字在前面,sElect 數字在後面。

列表手術

[編輯 | 編輯原始碼]

縮短列表

[編輯 | 編輯原始碼]

要縮短列表,可以透過從開頭或結尾刪除元素來實現,可以使用 choppopchop 會建立副本並從結尾開始操作,pop 會更改原始列表並從開頭開始操作。

chop 透過從列表末尾切除來返回新列表

(set 'vowels '("a" "e" "i" "o" "u"))
(chop vowels)
;-> ("a" "e" "i" "o")

(println vowels)
("a" "e" "i" "o" "u")                   ; original unchanged

chop 的可選第三個引數指定要刪除的元素數量

(chop vowels 3)
;-> ("a" "e")

(println vowels)
("a" "e" "i" "o" "u") ; original unchanged

pop(與 push 相反)會永久地從列表中刪除指定的元素,並使用列表索引而不是長度

(set 'vowels '("a" "e" "i" "o" "u"))

(pop vowels)                            ; defaults to 0-th element

(println vowels)
("e" "i" "o" "u")

(pop vowels -1)

(println vowels)
("e" "i" "o")

還可以使用 replace 從列表中刪除專案。

更改列表中的專案

[編輯 | 編輯原始碼]

可以使用以下函式輕鬆更改列表中的元素

  • replace 更改或刪除元素
  • swap 交換兩個元素
  • setf 設定元素的值
  • set-ref 搜尋巢狀列表並更改元素
  • set-ref-all 搜尋並更改巢狀列表中的每個元素

這些都是破壞性函式,就像 pushpopreversesort 一樣,會更改原始列表,因此請謹慎使用。

更改第 n 個元素

[編輯 | 編輯原始碼]

要將列表(或陣列)的第 n 個元素設定為另一個值,可以使用多功能的 setf 命令

(set 'data (sequence 100 110))
;-> (100 101 102 103 104 105 106 107 108 109 110)

(setf (data 5) 0)
;-> 0

data
;-> (100 101 102 103 104 0 106 107 108 109 110)

請注意 setf 函式如何返回剛剛設定的值 0,而不是更改後的列表。

此示例使用更快的隱式定址。當然,可以使用 nth 首先建立對第 n 個元素的引用

(set 'data (sequence 100 110))
;-> (100 101 102 103 104 105 106 107 108 109 110)

(setf (nth 5 data) 1)
;-> 1

data
;-> (100 101 102 103 104 1 106 107 108 109 110)

setf 必須用於儲存在符號中的列表或陣列或元素。無法向其傳遞原始資料

(setf (nth 5 (sequence 100 110)) 1)
;-> ERR: no symbol reference found

(setf (nth 5 (set 's (sequence 100 110))) 1)
; 'temporary' storage in symbol s
;-> 1
s
;-> (100 101 102 103 104 1 106 107 108 109 110)

使用它

[編輯 | 編輯原始碼]

有時當你使用setf時,你想在設定新值時引用舊值。為此,使用系統變數 $it。在setf表示式中,$it 包含舊值。因此,要將列表第一個元素的值增加 1

(set 'lst (sequence 0 9))
;-> (0 1 2 3 4 5 6 7 8 9)
(setf (lst 0) (+ $it 1))
;-> 1
lst
;-> (1 1 2 3 4 5 6 7 8 9)

你也可以對字串執行此操作。以下是“遞增”字串第一個字母的方法

(set 'str "cream")
;-> "cream"
(setf (str 0) (char (inc (char $it))))
;-> "d"
str
;-> "dream"

替換資訊:replace

[編輯 | 編輯原始碼]

你可以使用replace來更改或刪除列表中的元素。指定要更改的元素和要搜尋的列表,以及是否需要替換。

(set 'data (sequence 1 10)) 
(replace 5 data)                   ; no replacement specified
;-> (1 2 3 4 6 7 8 9 10)           ; the 5 has gone

(set 'data '(("a" 1) ("b" 2)))
(replace ("a" 1) data)             ; data is now (("b" 2))

每個匹配項都會被刪除。

replace 返回更改後的列表

(set 'data (sequence 1 10)) 
(replace 5 data 0)                 ; replace 5 with 0
;-> (1 2 3 4 0 6 7 8 9 10)

替換可以是簡單值,也可以是返回值的任何表示式。

(set 'data (sequence 1 10)) 
(replace 5 data (sequence 0 5))
;->(1 2 3 4 (0 1 2 3 4 5) 6 7 8 9 10)

replace 更新一組系統變數 $0, $1, $2,直到 $15,以及特殊變數 $it,用匹配的資料。對於列表替換,僅使用 $0 和 $it,它們儲存找到項的值,適合在替換表示式中使用。

(replace 5 data (list (dup $0 2)))    ; $0 holds 5
;-> (1 2 3 4 ((5 5)) 6 7 8 9 10)

有關係統變數及其在字串替換中的使用,請參閱 系統變數.

如果你沒有提供測試函式,= 會被使用

(set 'data (sequence 1 10)) 
(replace 5 data 0 =)
;-> (1 2 3 4 0 6 7 8 9 10)

(set 'data (sequence 1 10)) 
(replace 5 data 0)              ; = is assumed
;-> (1 2 3 4 0 6 7 8 9 10)

你可以讓replace找到透過除相等以外的其他測試的元素。在替換值之後提供測試函式

(set 'data (randomize (sequence 1 10)))
;-> (5 10 6 1 7 4 8 3 9 2)
(replace 5 data 0 <)  ; replace everything that 5 is less than
;-> (5 0 0 1 0 4 0 3 0 2)

測試可以是任何比較兩個值並返回真或假值的函式。這可能非常強大。假設你有一個包含姓名和分數的列表

(set 'scores '(
   ("adrian" 234 27 342 23 0) 
   ("hermann" 92 0 239 47 134) 
   ("neville" 71 2 118 0) 
   ("eric" 10 14 58 12 )))

對所有分數包含 0 的人新增數字有多容易?好吧,在match函式的幫助下,這很容易

(replace '(* 0 *) scores (list (first $0) (apply + (rest $0))) match)

(("adrian" 626) 
 ("hermann" 512) 
 ("neville" 191) 
 ("eric" 10 14 58 12))

在這裡,對於每個匹配的元素,替換表示式從姓名和分數之和構建一個列表。match 用作比較器函式 - 僅選擇匹配的列表元素進行彙總,因此 Eric 的分數沒有被彙總,因為他沒有設法獲得 0 分。

有關在字串上使用replace的更多資訊,請參閱 更改子字串.

修改列表

[編輯 | 編輯原始碼]

還有更強大的方法來修改列表中的元素。認識set-refset-ref-all

你可以使用這些函式來定位和修改元素,這些函式被設計為與巢狀列表配合使用。(另見 使用 XML 獲取一些應用。)

查詢並替換匹配的元素

[編輯 | 編輯原始碼]

set-ref 函式允許你修改列表中第一個匹配的元素

(set 'l '((aaa 100) (bbb 200)))
;-> ((aaa 100) (bbb 200))

要將 200 更改為 300,請像這樣使用set-ref

(set-ref 200 l 300)               ; change the first 200 to 300
;-> ((aaa 100) (bbb 300))

查詢並替換所有匹配的元素:set-ref-all

[編輯 | 編輯原始碼]

set-ref 查詢巢狀列表中第一個匹配的元素並進行更改;set-ref-all 可以替換每個匹配的元素。考慮以下包含行星資料的巢狀列表

 (("Mercury"
      (p-name "Mercury")
      (diameter 0.382)
      (mass 0.06)
      (radius 0.387)
      (period 0.241)
      (incline 7)
      (eccentricity 0.206)
      (rotation 58.6)
      (moons 0))
  ("Venus"
      (p-name "Venus")
      (diameter 0.949)
      (mass 0.82)
      (radius 0.72)
      (period 0.615)
      (incline 3.39)
      (eccentricity 0.0068)
      (rotation -243)
      (moons 0))
  ("Earth"
      (p-name "Earth")
      (diameter 1)
;      ...

如何將每個“incline”符號更改為“inclination”?使用set-ref-all很容易

(set-ref-all 'incline planets 'inclination)   ; key - list - replacement

這將返回一個列表,其中每個“incline”都被更改為“inclination”。

replace一樣,查詢匹配元素的預設測試是相等性。但你可以提供不同的比較函式。這就是你可以檢查行星列表並將每個月球值大於 9 的條目更改為“lots”而不是實際數字的方法。

(set-ref-all '(moons ?) planets (if (> (last $0) 9) "lots" (last $0)) match)

替換表示式比較月球的數量(結果的最後一個專案,儲存在 $0 中),如果它大於 9,則計算為“lots”。搜尋項使用match友好的萬用字元語法來制定,以匹配比較函式的選擇。

swap 函式可以交換列表中的兩個元素,或兩個符號的值。這會改變原始列表

(set 'fib '(1 2 1 3 5 8 13 21))
(swap (fib 1) (fib 2))                         ; list swap 
;-> (1 1 2 3 5 8 13 21)

fib
;-> (1 1 2 3 5 8 13 21)                 ; is 'destructive'

有用的是,swap 還可以交換兩個符號的值,而無需使用中間臨時變數。

(set 'x 1 'y 2)
(swap x y)
;-> 1
x
;-> 2
y
;-> 1

這種並行賦值有時可以使生活更輕鬆,例如在這個稍微不尋常的查詢斐波那契數的函式的迭代版本中

(define (fibonacci n)
 (let (current 1 next 0)
  (dotimes (j n)
    (print current " ")
    (inc next current)
    (swap current next))))
  
(fibonacci 20)
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765

使用兩個或多個列表

[編輯 | 編輯原始碼]

如果你有兩個列表,你可能想問一些問題,例如這兩個列表中有多少項?哪個專案僅在其中一個列表中?此列表中的專案在另一個列表中出現多少次?等等。這裡有一些有用的函式可以回答這些問題

  • difference 查詢兩個列表的集合差
  • intersect 查詢兩個列表的交集
  • count 統計一個列表中的每個元素在第二個列表中出現的次數

例如,要檢視句子中有多少個母音,請將已知的母音放在一個列表中,將句子放在另一個列表中(首先使用explode將句子轉換為字元列表)

(count '("a" "e" "i" "o" "u") (explode "the quick brown fox jumped over the lazy dog"))
;-> (1 4 1 4 2)

或者更短的

(count (explode "aeiou") (explode "the quick brown fox jumped over the lazy dog"))
;-> (1 4 1 4 2)

結果(1 4 1 4 2)意味著句子中有 1 個 a,4 個 e,1 個 i,4 個 o,2 個 u。

differenceintersect 是會讓你想起你在學校做的那些韋恩圖的函式(如果你去過教這些東西的學校)。在新 LISP 中,列表可以表示集合。

difference 返回一個列表,該列表包含第一個列表中不在第二個列表中的元素。例如,你可以比較系統上的兩個目錄以查詢存在於一個目錄中但不存在於另一個目錄中的檔案。你可以使用directory 函式來完成此操作。

(set 'd1 
 (directory "/Users/me/Library/Application Support/BBEdit"))
(set 'd2 
 (directory "/Users/me/Library/Application Support/TextWrangler"))

(difference d1 d2)
;-> ("AutoSaves" "Glossary" "HTML Templates" "Stationery" "Text Factories")

(difference d2 d1)
;-> ()

你放置的第一個列表非常重要!目錄d1 中有五個不在目錄d2 中的檔案或目錄,但d2 中沒有不在d1 中的檔案或目錄。

intersect 函式查詢存在於兩個列表中的元素。

(intersect d2 d1)
;-> ("." ".." ".DS_Store" "Language Modules" "Menu Scripts" "Plug-Ins" "Read Me.txt" "Scripts" "Unix Support")

這兩個函式都可以接受一個額外的引數,該引數控制是否保留或丟棄任何重複項。

你可以使用difference 函式來比較文字檔案的兩個版本。首先使用parse解析字串)將檔案拆分為行

(set 'd1 
 (parse (read-file "/Users/me/f1-(2006-05-29)-1.html") "\r" 0))

(set 'd2 
 (parse (read-file "/Users/me/f1-(2006-05-29)-6.html") "\r" 0))

(println (difference d1 d2))
(" <p class=\"body\">You could use this function to find" ...)

關聯列表

[編輯 | 編輯原始碼]

在新 LISP 中有多種技術可用於儲存資訊。一種非常簡單有效的技術是使用子列表的列表,其中每個子列表的第一個元素是。這種結構被稱為關聯列表,但你也可以將其視為字典,因為你首先透過查詢鍵元素來查詢列表中的資訊。

你也可以使用新 LISP 的上下文來實現字典。請參閱 介紹上下文.

你可以使用基本的列表函式來建立關聯列表。例如,你可以提供一個手工製作的引用的列表

(set 'ascii-chart '(("a" 97) ("b" 98) ("c" 99) 
; ...
))

或者你可以使用listpush等函式來構建關聯列表

(for (c (char "a") (char "z"))
 (push (list (char c) c) ascii-chart -1))

ascii-chart
;-> (("a" 97) ("b" 98) ("c" 99) ... ("z" 122))

它是一個子列表的列表,每個子列表具有相同的格式。子列表的第一個元素是鍵。鍵可以是字串、數字或符號。在鍵之後,你可以有任意數量的資料元素。

這是一個包含太陽系行星的一些資料的關聯列表

(set 'sol-sys
 '(("Mercury" 0.382 0.06 0.387 0.241 7.00 0.206 58.6 0) 
   ("Venus" 0.949 0.82 0.72 0.615 3.39 0.0068 -243 0) 
   ("Earth" 1.00 1.00 1.00 1.00 0.00 0.0167 1.00 1) 
   ("Mars" 0.53 0.11 1.52 1.88 1.85 0.0934 1.03 2) 
   ("Jupiter" 11.2 318 5.20 11.86 1.31 0.0484 0.414 63) 
   ("Saturn" 9.41 95 9.54 29.46 2.48 0.0542 0.426 49) 
   ("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27) 
   ("Neptune" 3.81 17.2 30.06 164.8 1.77 0.0086 0.671 13) 
   ("Pluto" 0.18 0.002 39.5 248.5 17.1 0.249 -6.5 3)
   )
   ; 0: Planet name 1: Equator diameter (earth) 2: Mass (earth) 
   ; 3: Orbital radius (AU) 4: Orbital period (years) 
   ; 5: Orbital Incline Angle 6: Orbital Eccentricity 
   ; 7: Rotation (days) 8: Moons
)

每個子列表都以字串開頭,即行星的名稱,後面跟著資料元素,在本例中是數字。行星名稱是。我在最後添加了一些註釋,因為我永遠不會記得元素 2 是行星的質量,以地球質量為單位。

您可以使用標準列表處理技術輕鬆訪問這些資訊,但 newLISP 提供了一些專門為處理這些字典或關聯列表而設計的定製函式。

  • assoc 查詢關鍵字的第一個出現位置並返回子列表。
  • lookup 在子列表中查詢關鍵字的值。

assoclookup 都接受子列表的第一個元素(鍵),並從相應的子列表中檢索一些資料。以下是 assoc 的實際應用,它返回子列表。

(assoc "Uranus" sol-sys)
;-> ("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27)

以下是 lookup,它更進一步,為您從子列表中的一個元素中獲取資料,或者如果您沒有指定元素,則獲取最後一個元素。

(lookup "Uranus" sol-sys)
;-> 27, moons - value of the final element of the sublist

(lookup "Uranus" sol-sys 2)
;-> 14.6, element 2 of the sublist is the planet's mass

這可以避免您使用 assocnth 的組合。

使用具有長子列表的關聯列表時,您可能會遇到的一個問題是,您可能無法記住索引號代表什麼。以下是一種解決方案。

(constant 'orbital-radius 3)
(constant 'au 149598000)                 ; 1 au in km
(println "Neptune's orbital radius is " 
 (mul au (lookup "Neptune" sol-sys orbital-radius)) 
 " kilometres")
Neptune's orbital radius is 4496915880 kilometres

在這裡,我們定義了 orbital-radiusau(天文單位)作為常量,您可以使用 orbital-radius 來引用子列表的正確列。這也有助於提高程式碼的可讀性。constant 函式類似於 set,但您提供的符號受到保護,防止被其他使用 set 的操作意外更改。您只能使用 constant 函式再次更改符號的值。

定義了這些常量後,以下是一個表示式,它列出了行星的不同軌道,單位為公里。

(dolist (planet-data sol-sys)             ; go through list
 (set 'planet (first planet-data))        ; get name 
 (set 'orb-rad
  (lookup planet sol-sys orbital-radius)) ; get radius
 (println 
    (format "%-8s %12.2f %18.0f" 
     planet 
     orb-rad 
     (mul au orb-rad))))
Mercury          0.39           57894426
Venus            0.72          107710560
Earth            1.00          149598000
Mars             1.52          227388960
Jupiter          5.20          777909600
Saturn           9.54         1427164920
Uranus          19.22         2875273560
Neptune         30.06         4496915880
Pluto           39.50         5909121000


當您想要操作浮點數時,請使用浮點運算子 addsubmuldiv,而不是 +-*/,後者用於整數(並將值轉換為整數)。

替換關聯列表中的子列表

[edit | edit source]

要更改儲存在關聯列表中的值,請像以前一樣使用 assoc 函式來查詢匹配的子列表,然後使用 setf 在該子列表上將值更改為新的子列表。

(setf (assoc "Jupiter" sol-sys) '("Jupiter" 11.2 318 5.20 11.86 1.31 0.0484 0.414 64))

向關聯列表新增新專案

[edit | edit source]

關聯列表也是普通列表,因此您可以對它們使用所有熟悉的 newLISP 技術。您想在我們的 sol-sys 列表中新增一個新的第十顆行星嗎?只需使用 push

(push '("Sedna" 0.093 0.00014 .0001 502 11500 0 20 0) sol-sys -1)

並檢查它是否已成功新增:

(assoc "Sedna" sol-sys)
;-> ("Sedna" 0.093 0.00014 0.0001 502 11500 0 20 0)

您可以使用 sort 對關聯列表進行排序。(請記住,sort 會永久更改列表。)以下是以質量排序的行星列表。由於您不想按名稱對它們進行排序,因此您使用自定義排序(請參閱 sort and randomize)來比較每對的質量(索引 2)值。

(constant 'mass 2)
(sort sol-sys (fn (x y) (> (x mass) (y mass))))

(println sol-sys)
("Jupiter" 11.2 318 5.2 11.86 1.31 0.0484 0.414 63) 
("Saturn" 9.41 95 9.54 29.46 2.48 0.0542 0.426 49) 
("Neptune" 3.81 17.2 30.06 164.8 1.77 0.0086 0.671 13) 
("Uranus" 3.98 14.6 19.22 84.01 0.77 0.0472 -0.718 27) 
("Earth" 1 1 1 1 0 0.0167 1 1) 
("Venus" 0.949 0.82 0.72 0.615 3.39 0.0068 -243 0) 
("Mars" 0.53 0.11 1.52 1.88 1.85 0.0934 1.03 2) 
("Mercury" 0.382 0.06 0.387 0.241 7 0.206 58.6 0) 
("Pluto" 0.18 0.002 39.5 248.5 17.1 0.249 -6.5 3)

您還可以輕鬆地將關聯列表中的資料與其他列表組合。

; restore to standard order - sort by orbit radius
(sort sol-sys (fn (x y) (< (x 3) (y 3))))   

; define Unicode symbols for planets
(set 'unicode-symbols 
  '(("Mercury" 0x263F )
    ("Venus" 0x2640 )
    ("Earth" 0x2641 )
    ("Mars" 0x2642 )
    ("Jupiter" 0x2643 )
    ("Saturn" 0x2644 ) 
    ("Uranus" 0x2645 )
    ("Neptune" 0x2646 )
    ("Pluto" 0x2647)))
(map 
 (fn (planet) 
 (println (char (lookup (first planet) unicode-symbols)) 
  "\t" 
 (first planet))) 
 sol-sys)
☿ (Unicode symbol for Mercury)
♀ (Unicode symbol for Venus)
♁ (Unicode symbol for Earth)
♂ (Unicode symbol for Mars)
♃ (Unicode symbol for Jupiter)
♄ (Unicode symbol for Saturn)
♅ (Unicode symbol for Uranus)
♆ (Unicode symbol for Neptune)
♇ (Unicode symbol for Pluto)

在這裡,我們建立了一個臨時行內函數,map 將其應用於 sol-sys 中的每個行星 - lookup 查詢行星名稱,並從 unicode-symbols 關聯列表中檢索該行星的 Unicode 符號。

您可以使用 pop-assoc 快速從關聯列表中刪除元素。

(pop-assoc (sol-sys "Pluto"))

這將從列表中刪除冥王星元素。

newLISP 以上下文的形式提供強大的資料儲存功能,您可以使用這些功能來構建字典、雜湊表、物件等等。您可以使用關聯列表來構建字典,並使用關聯列表函式來處理字典的內容。請參閱 Introducing contexts

您也可以使用資料庫引擎 - 請參閱 Using a SQLite database

find-all 和關聯列表

[edit | edit source]

另一種形式的 find-all 允許您在關聯列表中搜索與模式匹配的子列表。您可以使用萬用字元來指定模式。例如,以下是一個關聯列表。

(set 'symphonies 
  '((Beethoven 9)
    (Haydn 104)
    (Mozart 41)
    (Mahler 10)
    (Wagner 1)
    (Schumann 4)
    (Shostakovich 15)
    (Bruckner 9)))

要查詢所有以 9 結尾的子列表,請使用匹配模式 (? 9),其中問號匹配任何單個專案。

(find-all '(? 9) symphonies)
;-> ((Beethoven 9) (Bruckner 9))

(有關匹配模式的更多資訊 - 列表的萬用字元搜尋 - 請參閱 matching patterns in lists。)

您也可以在關聯列表之後使用額外的操作表示式使用此形式。

(find-all '(? 9) symphonies 
   (println (first $0) { wrote 9 symphonies.}))
Beethoven wrote 9 symphonies.
Bruckner wrote 9 symphonies.

在這裡,操作表示式使用 $0 來依次引用每個匹配的元素。

華夏公益教科書