newLISP/列表简介
列表在 newLISP 中无处不在 - LISP 代表 **列表处理** - 所以有许多有用的函数来处理列表并不奇怪。 将它们全部组织成一个合乎逻辑的描述性叙述非常困难,但这里是对大多数函数的介绍。
newLISP 的一个好处是,许多在列表上工作的函数也适用于字符串,所以你将在下一章中再次遇到许多这些函数,在那里它们将应用于字符串。
你可以直接构建一个列表并将其分配给一个符号。 引用列表以阻止它立即被求值。
(set 'vowels '("a" "e" "i" "o" "u"))
;-> ("a" "e" "i" "o" "u") ; an unevaluated list
列表通常由其他函数为你创建,但你也可以使用以下函数构建自己的列表。
- **list** 从表达式中创建一个新列表。
- **append** 将列表粘合在一起以形成一个新列表。
- **cons** 将元素添加到列表的开头或创建一个列表。
- **push** 在列表中插入一个新成员。
- **dup** 复制一个元素。
使用 **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** 是一个功能强大的命令,你可以使用它来创建一个新列表或将元素插入现有列表的任何位置。 将元素推入列表的开头会将所有元素向右移动一位,而将元素推入列表的末尾只会将它附加并创建一个新的最后一个元素。 你也可以将元素插入列表中间的任何位置。
尽管它具有构造性,但它在技术上是一个破坏性函数,因为它会永久更改目标列表,因此请谨慎使用它。 它返回插入的元素的值,而不是整个列表。
(set 'vowels '("e" "i" "o" "u"))
(push (char 97) vowels)
; returns "a"
; vowels is now ("a" "e" "i" "o" "u")
当你引用列表中元素的位置时,你使用的是从零开始的编号,这正是你作为经验丰富的程序员所期望的。
如果你没有指定位置或索引,**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 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 的优势在于它在一趟遍历中既遍历了列表,又将函数应用于列表中的每个项目。结果也是一个列表,这可能对后续处理更有用。
关于dolist 和apply 的更多内容可以在其他地方找到(参见 遍历列表,以及 Apply 和 map:将函数应用于列表)。
reverse
[edit | edit source]reverse 的作用正如你所预期的那样,它反转列表。它是一个破坏性函数,会永久改变列表。
(reverse '("A" "E" "I" "O" "U"))
;-> ("U" "O" "I" "E" "A")
sort 和 randomize
[edit | edit source]在某种程度上,randomize 和sort 是互补的,尽管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")
比较随机化之前和排序之后的data。sort 命令按数据类型和值对列表进行排序:整数在字符串之前,字符串在列表之前,依此类推。
默认排序方法是<,它将值排列成每个值都小于下一个值。
要更改排序方法,可以提供 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 个字符。可以使用fn 或lambda 来定义内联或匿名函数。
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
[edit | edit source]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-with 和ends-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 中没有一个单独的函数可以完成这项工作。相反,你有find、match、member、ref、filter、index 和count 等。使用哪一个取决于你想对问题“这个列表是否包含这个项目?”得到什么样的答案,以及列表是嵌套列表还是平面列表。
如果你想要一个简单的答案,并且只需进行快速的一级搜索,可以使用find。参见 find。
如果你也想要该项目和列表的剩余部分,可以使用member。参见 member。
如果你想要第一个出现的索引号,即使列表包含嵌套列表,也可以使用ref。参见 ref 和 ref-all。
如果你想要一个包含所有与搜索元素匹配的元素的新列表,可以使用find-all。参见 find-all。
如果你想知道列表是否包含某种元素模式,可以使用match。参见 匹配列表中的模式。
你可以使用filter、clean 和index 函数找到所有满足函数(内置函数或自定义函数)的列表项。参见 过滤列表:filter、clean 和 index。
exists 和for-all 函数检查列表中的元素,以查看它们是否通过测试。
如果你想在列表中找到元素只是为了将它们更改为其他东西,那么就不要先搜索它们,直接使用replace 就可以了。参见 替换信息:replace。你还可以使用set-ref 函数查找和替换列表元素。参见 查找和替换匹配的元素。
如果你想知道列表中某个项目的出现次数,可以使用count。参见 使用两个或更多列表。
让我们看一些这些函数的例子。
find
[edit | edit source]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”。请注意,与其他允许您提供比较函数的函数(即 find、ref、ref-all、replace 在用于列表时、set-ref、set-ref-all 和 sort)不同,比较函数 **必须** 提供。使用 < 函数,结果是“onion” 小于的列表。
(find-all "onion" food (print $0 { }) <)
;-> pickle
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-ref 和 set-ref-all 函数。请参见 查找和替换匹配元素。
在列表中查找内容的另一种方法是过滤列表。就像淘金一样,您可以创建过滤器,只保留您想要的内容,将不需要的内容冲洗掉。
filter 和 index 具有相同的语法,但 filter 返回列表元素,而 index 返回想要元素的索引号(索引),而不是列表元素本身。(这些函数不适用于嵌套列表。)
过滤函数 filter、clean 和 index 使用另一个函数来测试元素:根据元素是否通过测试,它会出现在结果列表中。您可以使用内置函数,也可以定义自己的函数。通常,用于测试并返回 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")
您也可以使用 filter 或 clean 来整理列表,然后使用它们 - 例如,删除由 parse 操作生成的空字符串。
您将在什么时候使用 index 而不是 filter 或 clean?嗯,当您之后要通过索引号而不是其值访问列表元素时,请使用 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...
exists 和for-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-all 和 replace 用于在列表中查找项目。通常,使用这些函数来查找与目标匹配的项目。但是,相等性只是默认测试:所有这些函数都可以接受可选的比较函数,该函数用于代替相等性测试。这意味着可以查找满足任何测试的列表元素。
以下示例使用 < 比较函数。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 使用此函数作为比较函数,查找列表中第一个使比较结果为真的元素。因为 tiger 比 dog 长,所以函数返回 3,即 dog 在列表中的索引。
可以将匿名(或 lambda)函数作为 find 函数的一部分提供,而不是编写单独的函数
(find "tiger" a-list (fn (x y) (> (length x) (length y))))
如果希望代码可读,可能会将更长或更复杂的比较器移到它们自己的单独(且有文档记录的)函数中。
还可以将比较函数与 ref、ref-all 和 replace 一起使用。
比较函数可以是任何接受两个值并返回 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 提取子列表
first 和 rest 函数是传统 car 和 cdr LISP 函数的更合理的名称,这些名称基于旧计算机硬件寄存器的名称。
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 数字在后面。
要缩短列表,可以通过从开头或结尾删除元素来实现,可以使用 chop 或 pop。chop 会创建副本并从结尾开始操作,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 搜索并更改嵌套列表中的每个元素
这些都是破坏性函数,就像 push、pop、reverse 和 sort 一样,会更改原始列表,因此请谨慎使用。
要将列表(或数组)的第 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来更改或删除列表中的元素。指定要更改的元素和要搜索的列表,以及是否需要替换。
(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-ref和set-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 查找嵌套列表中第一个匹配的元素并进行更改;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。
difference 和intersect 是会让你想起你在学校做的那些韦恩图的函数(如果你去过教这些东西的学校)。在新 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)
; ...
))
或者你可以使用list和push等函数来构建关联列表
(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 在子列表中查找关键字的值。
assoc 和 lookup 都接受子列表的第一个元素(键),并从相应的子列表中检索一些数据。以下是 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
这可以避免您使用 assoc 和 nth 的组合。
使用具有长子列表的关联列表时,您可能会遇到的一个问题是,您可能无法记住索引号代表什么。以下是一种解决方案。
(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-radius 和 au(天文单位)作为常量,您可以使用 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
当您想要操作浮点数时,请使用浮点运算符 add、sub、mul、div,而不是 +、-、* 和 /,后者用于整数(并将值转换为整数)。
替换关联列表中的子列表
[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 来依次引用每个匹配的元素。